Opracowywanie bibliotek przy użyciu interfejsu wiersza polecenia platformy .NET

W tym artykule opisano sposób pisania bibliotek dla platformy .NET przy użyciu interfejsu wiersza polecenia platformy .NET. Interfejs wiersza polecenia zapewnia wydajne i niskie środowisko, które działa w dowolnym obsługiwanym systemie operacyjnym. Nadal możesz tworzyć biblioteki za pomocą programu Visual Studio, a jeśli jest to preferowane środowisko , zapoznaj się z przewodnikiem programu Visual Studio.

Wymagania wstępne

Potrzebny jest zestaw .NET SDK zainstalowany na maszynie.

W sekcjach tego dokumentu do obsługi wersji programu .NET Framework potrzebny jest program .NET Framework zainstalowany na maszynie z systemem Windows.

Ponadto jeśli chcesz obsługiwać starsze elementy docelowe programu .NET Framework, musisz zainstalować pakiety docelowe lub pakiety deweloperskie ze strony pobierania programu .NET Framework. Zapoznaj się z tą tabelą:

Wersja systemu .NET Framework Co pobrać
4.6.1 Pakiet docelowy programu .NET Framework 4.6.1
4.6 Pakiet docelowy programu .NET Framework 4.6
4.5.2 .NET Framework 4.5.2 Developer Pack
4.5.1 .NET Framework 4.5.1 Developer Pack
4.5 Zestaw Windows Software Development Kit dla systemu Windows 8
4.0 Zestaw Windows SDK dla systemów Windows 7 i .NET Framework 4
2.0, 3.0 i 3.5 Środowisko uruchomieniowe programu .NET Framework 3.5 z dodatkiem SP1 (lub windows 8 lub nowszy)

Instrukcje dotyczące platformy .NET 5 lub .NET Standard

Możesz kontrolować strukturę docelową projektu, dodając ją do pliku projektu (csproj lub .fsproj). Aby uzyskać wskazówki dotyczące wyboru między określaniem wartości docelowej platformy .NET 5 lub .NET Standard, zobacz .NET 5+ i .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>

Jeśli chcesz użyć platformy .NET Framework w wersji 4.0 lub nowszej lub chcesz użyć interfejsu API dostępnego w programie .NET Framework, ale nie w programie .NET Standard (na przykład ), przeczytaj poniższe sekcje i dowiedz się, System.Drawingjak wieloskładniowy.

How to target .NET Framework

Uwaga

W tych instrukcjach założono, że na komputerze jest zainstalowany program .NET Framework. Zapoznaj się z tematem Wymagania wstępne , aby uzyskać zainstalowane zależności.

Pamiętaj, że niektóre wersje programu .NET Framework używane tutaj nie są już obsługiwane. Zapoznaj się z często zadawanymi pytaniami dotyczącymi zasad cyklu życia pomocy technicznej programu .NET Framework dotyczącymi nieobsługiwanych wersji.

Jeśli chcesz osiągnąć maksymalną liczbę deweloperów i projektów, użyj programu .NET Framework 4.0 jako celu odniesienia. Aby zastosować docelowy program .NET Framework, zacznij od użycia poprawnego programu Target Framework Moniker (TFM), który odpowiada wersji programu .NET Framework, którą chcesz obsługiwać.

Wersja systemu .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

Następnie wstawisz ten program TFM do TargetFramework sekcji pliku projektu. Na przykład poniżej przedstawiono sposób pisania biblioteki przeznaczonej dla programu .NET Framework 4.0:

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

I to wszystko. Mimo że ta kompilacja została skompilowana tylko dla programu .NET Framework 4, można użyć biblioteki w nowszych wersjach programu .NET Framework.

Jak wielotargetować

Uwaga

W poniższych instrukcjach założono, że na maszynie jest zainstalowany program .NET Framework. Zapoznaj się z sekcją Wymagania wstępne , aby dowiedzieć się, które zależności należy zainstalować i skąd je pobrać.

Może być konieczne skierowanie starszych wersji programu .NET Framework, gdy projekt obsługuje zarówno program .NET Framework, jak i platformę .NET. W tym scenariuszu, jeśli chcesz użyć nowszych interfejsów API i konstrukcji językowych dla nowszych obiektów docelowych, użyj #if dyrektyw w kodzie. Może być również konieczne dodanie różnych pakietów i zależności dla każdej platformy, dla której jest przeznaczona wartość docelowa, aby uwzględnić różne interfejsy API potrzebne dla każdego przypadku.

Załóżmy na przykład, że masz bibliotekę, która wykonuje operacje sieciowe za pośrednictwem protokołu HTTP. W przypadku platformy .NET Standard i programu .NET Framework w wersji 4.5 lub nowszej System.Net.Http można użyć HttpClient klasy z przestrzeni nazw. Jednak wcześniejsze wersje programu .NET Framework nie mają HttpClient klasy , więc można użyć WebClient klasy z System.Net przestrzeni nazw dla tych elementów.

Plik projektu może wyglądać następująco:

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

W tym miejscu zauważysz trzy istotne zmiany:

  1. Węzeł TargetFramework został zastąpiony przez TargetFrameworksprogram , a trzy maszyny TFM są wyrażane wewnątrz.
  2. <ItemGroup> Istnieje węzeł do net40 ściągania docelowego w jednym odwołaniu programu .NET Framework.
  3. Istnieje węzeł docelowy <ItemGroup>net45 ściągający w dwóch odwołaniach programu .NET Framework.

Symbole preprocesora

System kompilacji zna następujące symbole preprocesora używane w #if dyrektywach:

Platformy docelowe Symbole Dodatkowe symbole
(dostępne w zestawach .NET 5+ SDK)
Symbole platformy (dostępne tylko
podczas określania programu TFM specyficznego dla systemu operacyjnego)
.NET Framework NETFRAMEWORK, NET48, , , NET47NET462NET40NET471NET46NET35NET461NET452NET451NET45NET472NET20 NET48_OR_GREATER, NET472_OR_GREATER, , , NET462_OR_GREATERNET452_OR_GREATERNET47_OR_GREATERNET46_OR_GREATERNET35_OR_GREATERNET461_OR_GREATERNET451_OR_GREATERNET45_OR_GREATERNET40_OR_GREATERNET471_OR_GREATERNET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, , , NETSTANDARD1_5NETSTANDARD1_1NETSTANDARD1_6NETSTANDARD1_4NETSTANDARD1_3NETSTANDARD1_2NETSTANDARD2_0NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, , NETSTANDARD1_6_OR_GREATER, NETSTANDARD1_4_OR_GREATERNETSTANDARD1_5_OR_GREATER, NETSTANDARD1_3_OR_GREATER, , NETSTANDARD1_1_OR_GREATERNETSTANDARD1_2_OR_GREATERNETSTANDARD1_0_OR_GREATER
.NET 5+ (i .NET Core) NET, NET8_0, , , NET5_0NETCOREAPP3_0NET6_0NETCOREAPP3_1NETCOREAPP1_1NETCOREAPPNETCOREAPP2_2NETCOREAPP2_1NETCOREAPP2_0NET7_0NETCOREAPP1_0 NET8_0_OR_GREATER, NET7_0_OR_GREATER, , , NETCOREAPP3_1_OR_GREATERNETCOREAPP2_1_OR_GREATERNETCOREAPP1_1_OR_GREATERNET5_0_OR_GREATERNETCOREAPP3_0_OR_GREATERNETCOREAPP2_2_OR_GREATERNETCOREAPP2_0_OR_GREATERNET6_0_OR_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, , IOS, MACOSMACCATALYST, , TVOS, , WINDOWS
[OS][version] (na przykład IOS15_1),
[OS][version]_OR_GREATER (na przykład IOS15_1_OR_GREATER)

Uwaga

  • Symbole bez wersji są definiowane niezależnie od docelowej wersji.
  • Symbole specyficzne dla wersji są definiowane tylko dla docelowej wersji.
  • Symbole <framework>_OR_GREATER są definiowane dla docelowej wersji i wszystkich wcześniejszych wersji. Jeśli na przykład używasz platformy .NET Framework 2.0, zdefiniowane są następujące symbole: NET20, , NET20_OR_GREATERNET11_OR_GREATERi NET10_OR_GREATER.
  • Symbole NETSTANDARD<x>_<y>_OR_GREATER są definiowane tylko dla obiektów docelowych platformy .NET Standard, a nie dla obiektów docelowych implementujących platformę .NET Standard, takich jak .NET Core i .NET Framework.
  • Różnią się one od obiektów docelowych monikers (TFMs) używanych przez właściwość MSBuild TargetFramework i NuGet.

Oto przykład użycia kompilacji warunkowej dla elementu docelowego:

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

Jeśli skompilujesz ten projekt za pomocą dotnet buildpolecenia , zauważysz trzy katalogi w folderze bin/ :

net40/
net45/
netstandard2.0/

Każdy z nich zawiera .dll pliki dla każdego obiektu docelowego.

Jak testować biblioteki na platformie .NET

Ważne jest, aby móc testować na różnych platformach. Możesz użyć xUnit lub MSTest z pudełka. Obie te elementy doskonale nadają się do testowania jednostkowego biblioteki na platformie .NET. Sposób konfigurowania rozwiązania przy użyciu projektów testowych zależy od struktury rozwiązania. W poniższym przykładzie przyjęto założenie, że katalogi testowe i źródłowe działają w tym samym katalogu najwyższego poziomu.

Uwaga

Używa to niektórych poleceń interfejsu wiersza polecenia platformy .NET. Aby uzyskać więcej informacji, zobacz dotnet new and dotnet sln .

  1. Skonfiguruj rozwiązanie. Można to zrobić za pomocą następujących poleceń:

    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
    

    Spowoduje to utworzenie projektów i połączenie ich razem w rozwiązaniu. Katalog powinien SolutionWithSrcAndTest wyglądać następująco:

    /SolutionWithSrcAndTest
    |__SolutionWithSrcAndTest.sln
    |__MyProject/
    |__MyProject.Test/
    
  2. Przejdź do katalogu projektu testowego i dodaj odwołanie do MyProject.Test pliku .MyProject

    cd MyProject.Test
    dotnet add reference ../MyProject/MyProject.csproj
    
  3. Przywracanie pakietów i projektów kompilacji:

    dotnet restore
    dotnet build
    
  4. Sprawdź, czy narzędzie xUnit jest uruchamiane dotnet test , wykonując polecenie . Jeśli zdecydujesz się użyć narzędzia MSTest, moduł uruchamiający konsolę MSTest powinien zamiast tego uruchomić.

I to wszystko. Teraz możesz przetestować bibliotekę na wszystkich platformach przy użyciu narzędzi wiersza polecenia. Aby kontynuować testowanie teraz, gdy wszystko jest skonfigurowane, testowanie biblioteki jest bardzo proste:

  1. Wprowadź zmiany w bibliotece.
  2. Uruchom testy z wiersza polecenia w katalogu testowym za dotnet test pomocą polecenia .

Kod zostanie automatycznie skompilowany podczas wywoływania dotnet test polecenia.

Jak używać wielu projektów

Typowym zapotrzebowaniem na większe biblioteki jest umieszczenie funkcji w różnych projektach.

Wyobraź sobie, że chcesz utworzyć bibliotekę, która może być zużywana w idiomatycznych językach C# i F#. Oznaczałoby to, że użytkownicy biblioteki używają jej w sposób naturalny dla języka C# lub F#. Na przykład w języku C# możesz użyć biblioteki w następujący sposób:

using AwesomeLibrary.CSharp;

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

W języku F# może to wyglądać następująco:

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
}

Takie scenariusze użycia oznaczają, że uzyskiwane interfejsy API muszą mieć inną strukturę dla języka C# i języka F#. Typowym podejściem do osiągnięcia tego celu jest uwzględnienie całej logiki biblioteki w podstawowym projekcie przy użyciu projektów języka C# i F# definiujących warstwy interfejsu API wywołujące ten podstawowy projekt. W pozostałej części sekcji będą używane następujące nazwy:

  • AwesomeLibrary.Core — podstawowy projekt zawierający całą logikę biblioteki
  • AwesomeLibrary.CSharp — projekt z publicznymi interfejsami API przeznaczonymi do użycia w języku C#
  • AwesomeLibrary.FSharp — projekt z publicznymi interfejsami API przeznaczonymi do użycia w języku F#

Następujące polecenia można uruchomić w terminalu, aby utworzyć taką samą strukturę jak w tym przewodniku:

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

Spowoduje to dodanie trzech projektów powyżej i pliku rozwiązania, który łączy je ze sobą. Utworzenie pliku rozwiązania i łączenie projektów umożliwi przywracanie i kompilowanie projektów z najwyższego poziomu.

Odwoływanie się do projektu

Najlepszym sposobem odwołowania się do projektu jest użycie interfejsu wiersza polecenia platformy .NET w celu dodania odwołania do projektu. W katalogach projektów AwesomeLibrary.CSharp i AwesomeLibrary.FSharp można uruchomić następujące polecenie:

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

Pliki projektu zarówno AwesomeLibrary.CSharp, jak i AwesomeLibrary.FSharp będą teraz odwoływać się do AwesomeLibrary.Core jako ProjectReference elementu docelowego. Możesz to sprawdzić, sprawdzając pliki projektu i wyświetlając w nich następujące elementy:

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

Tę sekcję można dodać do każdego pliku projektu ręcznie, jeśli nie chcesz używać interfejsu wiersza polecenia platformy .NET.

Tworzenie struktury rozwiązania

Innym ważnym aspektem rozwiązań wieloprojektowych jest ustanowienie dobrej ogólnej struktury projektu. Możesz jednak zorganizować kod i tak długo, jak łączysz każdy projekt z plikiem rozwiązania za pomocą dotnet sln addpolecenia , będzie można uruchomić dotnet restore i dotnet build na poziomie rozwiązania.