Compartir a través de


Desarrollo de bibliotecas con la CLI de .NET

En este artículo se explica cómo escribir bibliotecas para .NET mediante la CLI de .NET. La CLI proporciona una experiencia eficaz y de bajo nivel que funciona en todos los SO compatibles. Todavía puede compilar bibliotecas con Visual Studio y, si es su experiencia preferida referir a la guía de Visual Studio.

Requisitos previos

Necesita el SDK .NET instalado en el equipo.

Para las secciones de este documento que tratan las versiones de .NET Framework, necesita el .NET Framework instalado en un equipo Windows.

Además, si desea admitir destinos anteriores de .NET Framework, debe instalar paquetes de destino o paquetes de desarrollador desde la página de descargas de .NET Framework. Consulte la tabla siguiente:

versión de .NET Framework Qué debe descargar
4.6.1 paquete de destino de .NET Framework 4.6.1
4.6 paquete de destino de .NET Framework 4.6
4.5.2 paquete para desarrolladores de .NET Framework 4.5.2
4.5.1 paquete para desarrolladores de .NET Framework 4.5.1
4.5 Kit de desarrollo de software de Windows para Windows 8
4.0 SDK de Windows para Windows 7 y .NET Framework 4
2.0, 3.0 y 3.5 .NET Framework 3.5 SP1 Runtime (o versión de Windows 8+)

Cómo dirigirse a .NET 5+ o .NET Estándar

Para controlar la plataforma de destino del proyecto, agréguela al archivo de proyecto ( .csproj o .fsproj). Para obtener instrucciones sobre cómo elegir entre .NET 5+ o .NET Estándar, consulte .NET 5+ y .NET Standard.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

Si quiere tener como destino .NET Framework versiones 4.0 o posteriores, o desea usar una API disponible en .NET Framework, pero no en .NET Estándar (por ejemplo, System.Drawing), lea las secciones siguientes y aprenda a multitarget.

Cómo seleccionar .NET Framework

Nota:

En estas instrucciones se supone que tiene .NET Framework instalado en el equipo. Consulte los requisitos previos para instalar las dependencias.

Tenga en cuenta que algunas de las versiones de .NET Framework que se usan aquí ya no se admiten. Consulte las preguntas frecuentes sobre la política de ciclo de vida de soporte de .NET Framework sobre las versiones no admitidas.

Si desea alcanzar el número máximo de desarrolladores y proyectos, use .NET Framework 4.0 como su objetivo base. Para tener como destino .NET Framework, empiece por usar el Moniker de la plataforma de destino (TFM) correcto que corresponde a la versión de .NET Framework que desea admitir.

versión de .NET Framework TFM
.NET Framework 2.0 net20
.NET Framework 3.0 net30
.NET Framework 3.5 net35
.NET Framework 4.0 net40
.NET Framework 4.5 net45
.NET Framework 4.5.1 net451
.NET Framework 4.5.2 net452
.NET Framework 4.6 net46
.NET Framework 4.6.1 net461
.NET Framework 4.6.2 net462
.NET Framework 4.7 net47
.NET Framework 4.8 net48

Después, inserte este TFM en la sección correspondiente de su archivo del proyecto. Por ejemplo, aquí se muestra cómo escribiría una biblioteca destinada a .NET Framework 4.0:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net40</TargetFramework>
  </PropertyGroup>
</Project>

Y listo. Aunque este compilado solo para .NET Framework 4, puede usar la biblioteca en versiones más recientes de .NET Framework.

Cómo multitargettear

Nota:

En las instrucciones siguientes se supone que tiene instalado .NET Framework en el equipo. Consulte la sección de requisitos previos para información sobre las dependencias que debe instalar y dónde descargarlas.

Es posible que tenga que tener como destino versiones anteriores de .NET Framework cuando el proyecto admita .NET Framework y .NET. En este escenario, si desea usar API más recientes y construcciones de lenguaje para los destinos más recientes, use las directivas en el código. También es posible que tenga que agregar distintos paquetes y dependencias para cada plataforma que tiene como destino para incluir las distintas API necesarias para cada caso.

Por ejemplo, digamos que tiene una biblioteca que realiza operaciones de red a través de HTTP. Para .NET Standard y las versiones 4.5 o posteriores de .NET Framework, puede utilizar la clase HttpClient del espacio de nombres System.Net.Http. Sin embargo, las versiones anteriores de .NET Framework no tienen la clase HttpClient, por lo que podría usar la clase WebClient del espacio de nombres System.Net para esos en su lugar.

Su archivo del proyecto podría tener la siguiente apariencia:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net40;net45</TargetFrameworks>
  </PropertyGroup>

  <!-- Need to conditionally bring in references for the .NET Framework 4.0 target -->
  <ItemGroup Condition="'$(TargetFramework)' == 'net40'">
    <Reference Include="System.Net" />
  </ItemGroup>

  <!-- Need to conditionally bring in references for the .NET Framework 4.5 target -->
  <ItemGroup Condition="'$(TargetFramework)' == 'net45'">
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Threading.Tasks" />
  </ItemGroup>
</Project>

Observará tres cambios principales aquí:

  1. El nodo se ha reemplazado por , y se expresan tres TFM dentro.
  2. Hay un nodo <ItemGroup> para el net40 destino que incorpora una referencia de .NET Framework.
  3. Hay un nodo <ItemGroup> para el objetivo net45 que incluye dos referencias de .NET Framework.

Símbolos de preprocesador

El sistema de compilación conoce los siguientes símbolos del preprocesador que se usan en las directivas :

Versiones de .NET Framework de destino Símbolos Símbolos adicionales
(disponible en SDK de .NET 5 y superiores)
Símbolos de plataforma (disponibles únicamente
cuando se especifica un TFM específico del sistema operativo)
.NET Framework , , , , , , , , , , , , , , ,
.NET Estándar , , , , , , , , , , , , , , , ,
.NET 5+ (y .NET Core) , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
(por ejemplo ),
(por ejemplo, )

Nota:

  • Los símbolos sin versión se definen independientemente de la versión de destino.
  • Los símbolos específicos de la versión solo se definen para la versión de destino.
  • Los símbolos se definen para la versión de destino y todas las versiones anteriores. Por ejemplo, si tiene como destino .NET Framework 2.0, se definen los símbolos siguientes: NET20, NET20_OR_GREATER, NET11_OR_GREATER y NET10_OR_GREATER.
  • Los símbolos de NETSTANDARD<x>_<y>_OR_GREATER solo se definen para destinos estándar de .NET y no para destinos que implementan .NET Standard, como .NET Core y .NET Framework.
  • Son diferentes de los identificadores del marco de trabajo de destino (TFM) utilizados por la propiedad MSBuild y NuGet.

Aquí se muestra un ejemplo en el que se usa la compilación condicional por destino:

using System;
using System.Text.RegularExpressions;
#if NET40
// This only compiles for the .NET Framework 4 targets
using System.Net;
#else
 // This compiles for all other targets
using System.Net.Http;
using System.Threading.Tasks;
#endif

namespace MultitargetLib
{
    public class Library
    {
#if NET40
        private readonly WebClient _client = new WebClient();
        private readonly object _locker = new object();
#else
        private readonly HttpClient _client = new HttpClient();
#endif

#if NET40
        // .NET Framework 4.0 does not have async/await
        public string GetDotNetCount()
        {
            string url = "https://www.dotnetfoundation.org/";

            var uri = new Uri(url);

            string result = "";

            // Lock here to provide thread-safety.
            lock(_locker)
            {
                result = _client.DownloadString(uri);
            }

            int dotNetCount = Regex.Matches(result, ".NET").Count;

            return $"Dotnet Foundation mentions .NET {dotNetCount} times!";
        }
#else
        // .NET Framework 4.5+ can use async/await!
        public async Task<string> GetDotNetCountAsync()
        {
            string url = "https://www.dotnetfoundation.org/";

            // HttpClient is thread-safe, so no need to explicitly lock here
            var result = await _client.GetStringAsync(url);

            int dotNetCount = Regex.Matches(result, ".NET").Count;

            return $"dotnetfoundation.org mentions .NET {dotNetCount} times in its HTML!";
        }
#endif
    }
}

Si construye este proyecto con [nombre del lenguaje/framework], observará tres directorios dentro de la carpeta:

net40/
net45/
netstandard2.0/

Cada uno de ellos contiene los archivos para cada destino.

Prueba de bibliotecas en .NET

Es importante poder probar en varias plataformas. Puede usar xUnit o MSTest de fábrica. Ambos son perfectamente adecuados para pruebas unitarias de la biblioteca en .NET. Cómo configurar la solución con proyectos de prueba dependerá de la estructura de la solución. En el ejemplo siguiente se presupone que los directorios de origen y de prueba residen en el mismo directorio de nivel superior.

Nota:

Usa algunos comandos .NET CLI. Vea dotnet new y dotnet sln para obtener más información.

  1. Configure su solución. Puede hacerlo con los siguientes comandos:

    mkdir SolutionWithSrcAndTest
    cd SolutionWithSrcAndTest
    dotnet new sln
    dotnet new classlib -o MyProject
    dotnet new xunit -o MyProject.Test
    dotnet sln add MyProject/MyProject.csproj
    dotnet sln add MyProject.Test/MyProject.Test.csproj
    

    Esto creará proyectos y se vincularán conjuntamente en una solución. Su directorio para debe tener el siguiente aspecto:

    /SolutionWithSrcAndTest
    |__SolutionWithSrcAndTest.sln
    |__MyProject/
    |__MyProject.Test/
    
  2. Vaya al directorio del proyecto de prueba y agregue una referencia a desde .

    cd MyProject.Test
    dotnet reference add ../MyProject/MyProject.csproj
    
  3. Restaurar paquetes y crear proyectos:

    dotnet restore
    dotnet build
    
  4. Compruebe que xUnit se ejecuta mediante la ejecución del comando . Si decide usar MSTest, entonces debe ejecutarse el ejecutor de consola MSTest en su lugar.

Y listo. Ahora puede probar la biblioteca en todas las plataformas; para ello, use herramientas de línea de comandos. Para seguir con las pruebas ahora que ya está todo configurado, probar la biblioteca es un proceso muy simple:

  1. Haga los cambios en la biblioteca.
  2. Ejecute las pruebas desde la línea de comandos, en el directorio de prueba, con el comando .

El código se recompilará automáticamente cuando invoque el comando .

Uso de varios proyectos

Una necesidad en común de las bibliotecas de mayor tamaño es ubicar la funcionalidad en distintos proyectos.

Imagine que desea crear una biblioteca que se pueda consumir en el estilo idiomático de C# y F#. Eso significaría que los consumidores de tu biblioteca la consumirían de manera natural para C# o F#. Por ejemplo, en C#, podría consumir la biblioteca de la siguiente manera:

using AwesomeLibrary.CSharp;

public Task DoThings(Data data)
{
    var convertResult = await AwesomeLibrary.ConvertAsync(data);
    var result = AwesomeLibrary.Process(convertResult);
    // do something with result
}

En F#, sería de la siguiente manera:

open AwesomeLibrary.FSharp

let doWork data = async {
    let! result = AwesomeLibrary.AsyncConvert data // Uses an F# async function rather than C# async method
    // do something with result
}

Escenarios de consumo similares a este significan que las API a las que se tiene acceso deben tener una estructura distinta para C# y para F#. Un enfoque común para lograrlo es factorizar toda la lógica de una biblioteca en un proyecto central, con los proyectos de C# y F# definiendo los niveles de API que hacen llamadas a ese proyecto central. En el resto de la sección se usarán los siguientes nombres:

  • AwesomeLibrary.Core: un proyecto central que contiene toda la lógica de la biblioteca
  • AwesomeLibrary.CSharp: un proyecto con API públicas pensado para el consumo en C#
  • AwesomeLibrary.FSharp: un proyecto con API públicas pensado para el consumo en F#

Puede ejecutar los siguientes comandos en su terminal para generar la misma estructura de esta guía:

mkdir AwesomeLibrary && cd AwesomeLibrary
dotnet new sln
mkdir AwesomeLibrary.Core && cd AwesomeLibrary.Core && dotnet new classlib
cd ..
mkdir AwesomeLibrary.CSharp && cd AwesomeLibrary.CSharp && dotnet new classlib
cd ..
mkdir AwesomeLibrary.FSharp && cd AwesomeLibrary.FSharp && dotnet new classlib -lang "F#"
cd ..
dotnet sln add AwesomeLibrary.Core/AwesomeLibrary.Core.csproj
dotnet sln add AwesomeLibrary.CSharp/AwesomeLibrary.CSharp.csproj
dotnet sln add AwesomeLibrary.FSharp/AwesomeLibrary.FSharp.fsproj

Esto agregará los tres proyectos anteriores y un archivo de solución que los vincula conjuntamente. Crear el archivo de solución y vincular los proyectos le permitirá restaurar y crear proyectos desde un nivel superior.

Referencias entre proyectos

La mejor manera de hacer referencia a un proyecto es usar la CLI de .NET para agregar una referencia de proyecto. Desde los directorios del proyecto AwesomeLibrary.CSharp y AwesomeLibrary.FSharp, puede ejecutar el siguiente comando:

dotnet reference add ../AwesomeLibrary.Core/AwesomeLibrary.Core.csproj

Los archivos del proyecto para AwesomeLibrary.CSharp y AwesomeLibrary.FSharp ahora referenciarán a AwesomeLibrary.Core como objetivo. Puede comprobar esto inspeccionando los archivos del proyecto y observando lo siguiente en ellos:

<ItemGroup>
  <ProjectReference Include="..\AwesomeLibrary.Core\AwesomeLibrary.Core.csproj" />
</ItemGroup>

Puede agregar esta sección a cada archivo de proyecto manualmente si prefiere no usar la CLI de .NET.

Estructura de una solución

Otro aspecto importante de las soluciones de varios proyectos es establecer una buena estructura de proyecto general. Puede organizar el código de la manera que quiera, y siempre y cuando vincule cada proyecto a su archivo de solución con , podrá ejecutar y en el nivel de solución.