Développer des bibliothèques avec l’interface CLI .NET

Cet article explique comment écrire des bibliothèques pour .NET à l’aide de l’interface CLI .NET. L’interface CLI fournit une expérience efficace et de bas niveau qui fonctionne sur tous les systèmes d’exploitation pris en charge. Vous pouvez toujours créer des bibliothèques avec Visual Studio, et si c’est ce que vous préférez, consultez le guide Visual Studio.

Prérequis

Le SDK .NET doit être installé sur votre ordinateur.

Pour accéder aux sections de ce document concernant les versions du .NET Framework, vous devez installer le .NET Framework sur un ordinateur Windows.

Par ailleurs, si vous souhaitez prendre en charge des cibles .NET Framework plus anciennes, vous devez installer des packs de ciblage ou de développement à partir de la page de téléchargements de .NET Framework. Reportez-vous au tableau suivant :

Version du .NET Framework À télécharger
4.6.1 Pack de ciblage .NET Framework 4.6.1
4.6 Pack de ciblage .NET Framework 4.6
4.5.2 Pack du développeur .NET Framework 4.5.2
4.5.1 Pack du développeur .NET Framework 4.5.1
4.5 SDK Windows pour Windows 8
4.0 SDK pour Windows 7 et .NET Framework 4
2.0, 3.0 et 3.5 Runtime .NET Framework 3.5 SP1 (ou version Windows 8+)

Comment cibler .NET 5+ ou .NET Standard

Vous contrôlez le framework cible de votre projet en l’ajoutant à votre fichier projet (.csproj ou .fsproj). Pour obtenir des conseils sur la façon de choisir entre le ciblage de .NET 5+ ou .NET Standard, consultez .NET 5+ et .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 vous voulez cibler les versions .NET Framework 4.0 ou antérieures, ou que vous voulez utiliser une API disponible dans le .NET Framework, mais pas dans .NET Standard (par exemple, System.Drawing), lisez les sections suivantes et découvrez comment réaliser un multiciblage.

Comment cibler .NET Framework

Notes

Les instructions suivantes supposent que le .NET Framework est installé sur votre ordinateur. Reportez-vous aux Prérequis pour installer les dépendances.

N’oubliez pas que certaines des versions du .NET Framework utilisées ici ne sont plus prises en charge. Reportez-vous au Forum aux questions sur la politique de support de Microsoft .NET Framework concernant les versions non prises en charge.

Si vous voulez atteindre le nombre maximal de développeurs et de projets, utilisez .NET Framework 4.0 comme cible de base de référence. Pour cibler .NET Framework, commencez par utiliser le moniker de framework cible approprié qui correspond à la version de.NET Framework que vous voulez prendre en charge.

Version du .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

Insérez ensuite ce Moniker du Framework cible dans la section TargetFramework de votre fichier projet. Par exemple, voici comment écrire une bibliothèque qui cible .NET Framework 4.0 :

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

C’est tout ! Bien que ce code se compilait uniquement pour .NET Framework 4, vous pouvez utiliser la bibliothèque sur des versions plus récentes de .NET Framework.

Comment réaliser un multiciblage

Notes

Les instructions suivantes supposent que le .NET Framework est installé sur votre ordinateur. Reportez-vous à la section Prérequis pour savoir quelles dépendances doivent être installées et à partir d’où les télécharger.

Vous devrez peut-être cibler des versions antérieures de .NET Framework lorsque votre projet prend en charge .NET Framework et .NET. Dans ce scénario, si vous voulez utiliser des API et des constructions de langage plus récentes pour des cibles plus récentes, utilisez des directives #if dans votre code. Vous pouvez aussi avoir besoin d’ajouter différents packages et différentes dépendances pour chaque plateforme que vous ciblez pour inclure les différentes API nécessaires à chaque cas.

Supposons, par exemple, que vous avez une bibliothèque qui effectue des opérations réseau sur HTTP. Pour .NET Standard et les versions .NET Framework 4.5 ou ultérieures, vous pouvez utiliser la classe HttpClient à partir de l’espace de noms System.Net.Http. Toutefois, les versions antérieures du .NET Framework ne disposent pas de la classe HttpClient. Vous pourriez donc utiliser la classe WebClient à partir de l’espace de noms System.Net à la place.

Voici à quoi peut ressembler votre fichier projet :

<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>

Notez trois changements majeurs ici :

  1. Le nœud TargetFramework a été remplacé par TargetFrameworks, et trois Monikers du Framework cible sont exprimés à l’intérieur.
  2. Un nœud <ItemGroup> est présent pour l’extraction de cible net40 dans une référence .NET Framework.
  3. Un nœud <ItemGroup> est présent pour l’extraction de cible net45 dans deux références .NET Framework.

Symboles du préprocesseur

Le système de build est informé des symboles de préprocesseur suivants utilisés dans les directives #if:

Versions cibles de .NET Framework symboles Symboles supplémentaires
(disponibles dans les SDK .NET 5+)
Symboles de plateforme (disponibles uniquement
lorsque vous spécifiez un moniker de framework cible spécifique au système d’exploitation)
.NET Framework NETFRAMEWORK, NET48, NET472, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, NET35, NET20 NET48_OR_GREATER, NET472_OR_GREATER, NET471_OR_GREATER, NET47_OR_GREATER, NET462_OR_GREATER, NET461_OR_GREATER, NET46_OR_GREATER, NET452_OR_GREATER, NET451_OR_GREATER, NET45_OR_GREATER, NET40_OR_GREATER, NET35_OR_GREATER, NET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, NETSTANDARD1_1, NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, NETSTANDARD1_6_OR_GREATER, NETSTANDARD1_5_OR_GREATER, NETSTANDARD1_4_OR_GREATER, NETSTANDARD1_3_OR_GREATER, NETSTANDARD1_2_OR_GREATER, NETSTANDARD1_1_OR_GREATER, NETSTANDARD1_0_OR_GREATER
.NET 5+ (et .NET Core) NET, NET8_0, NET7_0, NET6_0, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0 NET8_0_OR_GREATER, NET7_0_OR_GREATER, NET6_0_OR_GREATER, NET5_0_OR_GREATER, NETCOREAPP3_1_OR_GREATER, NETCOREAPP3_0_OR_GREATER, NETCOREAPP2_2_OR_GREATER, NETCOREAPP2_1_OR_GREATER, NETCOREAPP2_0_OR_GREATER, NETCOREAPP1_1_OR_GREATER, NETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, IOS, MACCATALYST, MACOS, TVOS, WINDOWS,
[OS][version] (par exemple, IOS15_1),
[OS][version]_OR_GREATER (par exemple IOS15_1_OR_GREATER)

Notes

  • Les symboles sans version sont définis indépendamment de la version que vous ciblez.
  • Les symboles spécifiques à la version sont définis uniquement pour la version que vous ciblez.
  • Les symboles <framework>_OR_GREATER sont définis pour la version que vous ciblez et toutes les versions antérieures. Par exemple, si vous ciblez .NET Framework 2.0, les symboles suivants sont définis : NET20, NET20_OR_GREATER, NET11_OR_GREATER et NET10_OR_GREATER.
  • Les symboles NETSTANDARD<x>_<y>_OR_GREATER sont définis uniquement pour les cibles .NET Standard, et non pour les cibles qui implémentent .NET Standard, telles que .NET Core et .NET Framework.
  • Ils sont différents des monikers de framework cible utilisés par la propriété MSBuild TargetFramework et NuGet.

Voici un exemple d’utilisation de la compilation conditionnelle par cible :

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 vous générez ce projet avec dotnet build, notez la présence de trois répertoires sous le dossier bin/ :

net40/
net45/
netstandard2.0/

Chacun d’entre eux contient les fichiers .dll pour chaque cible.

Comment tester des bibliothèques sur .NET

Il est important de pouvoir effectuer des tests sur plusieurs plateformes. Vous pouvez utiliser xUnit ou MSTest dans leur version d’origine. Les deux conviennent parfaitement à la réalisation de tests unitaires sur votre bibliothèque sur .NET. La façon dont vous configurez votre solution avec des projets de test dépend de la structure de votre solution. L’exemple suivant part du principe que les répertoires de test et source résident dans le même répertoire de premier niveau.

Notes

Cet exemple utilise certaines commandes de l’interface CLI .NET. Pour plus d’informations, consultez dotnet new et dotnet sln.

  1. Configurez votre solution. Pour cela, utilisez la commande suivante :

    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
    

    Cette commande crée des projets et les relie dans une solution. Votre répertoire pour SolutionWithSrcAndTest doit ressembler à ceci :

    /SolutionWithSrcAndTest
    |__SolutionWithSrcAndTest.sln
    |__MyProject/
    |__MyProject.Test/
    
  2. Accédez au répertoire du projet de test et ajoutez une référence à MyProject.Test à partir de MyProject.

    cd MyProject.Test
    dotnet add reference ../MyProject/MyProject.csproj
    
  3. Restaurez les packages et générez les projets :

    dotnet restore
    dotnet build
    
  4. Exécutez la commande dotnet test pour vérifier que xUnit s’exécute. Si vous avez choisi d’utiliser MSTest, le Test Runner de console MSTest doit s’exécuter à la place.

C’est tout ! Vous pouvez maintenant tester votre bibliothèque sur toutes les plateformes à l’aide des outils en ligne de commande. Maintenant que vous avez tout configuré, le test de votre bibliothèque est très simple :

  1. Apportez des modifications à votre bibliothèque.
  2. Exécutez les tests à partir de la ligne de commande, dans votre répertoire de test, avec la commande dotnet test.

Votre code est automatiquement régénéré quand vous appelez la commande dotnet test.

Comment utiliser plusieurs projets

Les bibliothèques plus volumineuses ont souvent besoin de placer des fonctionnalités dans différents projets.

Imaginez que vous vouliez créer une bibliothèque pouvant être utilisée en langage C# et F#. Cela signifierait que les consommateurs de votre bibliothèque les utilisent dans des formes qui sont naturelles en C# ou F#. Par exemple, en C#, vous pouvez utiliser la bibliothèque de la façon suivante :

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#, le code peut se présenter comme suit :

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
}

Les scénarios de consommation tels que celui-ci signifient que les API auxquelles vous accédez ont une structure différente en C# et F#. Pour réaliser ceci, il est courant de factoriser toute la logique d’une bibliothèque dans un projet principal et d’avoir des projets C# et F# définissant les couches API qui effectuent des appels à ce projet principal. Le reste de la section utilise les noms suivants :

  • AwesomeLibrary.Core : projet principal qui contient toute la logique de la bibliothèque
  • AwesomeLibrary.CSharp : projet avec des API publiques destinées à être consommées en C#
  • AwesomeLibrary.CSharp : projet avec des API publiques destinées à être consommées en F#

Vous pouvez exécuter les commandes suivantes dans votre terminal pour produire la même structure que ce guide :

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

Cette opération ajoute les trois projets ci-dessus et un fichier solution qui les relie. Le fait de créer le fichier solution et de lier des projets vous permet de restaurer et de générer des projets à partir d’un niveau supérieur.

Références entre projets

La meilleure façon de référencer un projet consiste à utiliser l’interface de ligne de commande .NET pour ajouter une référence de projet. À partir des répertoires de projet AwesomeLibrary.CSharp et de AwesomeLibrary.FSharp, vous pouvez exécuter la commande suivante :

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

Les fichiers projet pour AwesomeLibrary.CSharp et AwesomeLibrary.FSharp référencent désormais AwesomeLibrary.Core comme cible de ProjectReference. Pour le vérifier, confirmez la présence des éléments suivants dans les fichiers projet :

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

Vous pouvez ajouter manuellement cette section à chaque fichier projet si vous préférez ne pas utiliser l’interface de ligne de commande .NET.

Structurer une solution

Un autre aspect important des solutions à projets multiples est la mise en place d’une bonne structure de projet globale. Vous pouvez organiser le code comme vous le souhaitez, et tant que vous liez chaque projet à votre fichier solution avec dotnet sln add, vous pouvez exécuter dotnet restore et dotnet build au niveau de la solution.