Agent Skills: C# Library Development

C#-specific library development patterns. Use when creating .NET class libraries, designing NuGet packages, configuring project files, implementing strong naming and versioning, writing XML documentation, unit testing with xUnit/NUnit, source generators, and multi-targeting. Extends meta-library-dev with .NET tooling and ecosystem patterns.

UncategorizedID: arustydev/ai/lang-csharp-library-dev

Repository

aRustyDevLicense: AGPL-3.0
72

Install this agent skill to your local

pnpm dlx add-skill https://github.com/aRustyDev/agents/tree/HEAD/content/skills/lang-csharp-library-dev

Skill Files

Browse the full folder contents for lang-csharp-library-dev.

Download Skill

Loading file tree…

content/skills/lang-csharp-library-dev/SKILL.md

Skill Metadata

Name
lang-csharp-library-dev
Description
C#-specific library development patterns. Use when creating .NET class libraries, designing NuGet packages, configuring project files, implementing strong naming and versioning, writing XML documentation, unit testing with xUnit/NUnit, source generators, and multi-targeting. Extends meta-library-dev with .NET tooling and ecosystem patterns.

C# Library Development

C#-specific patterns for .NET library development. This skill extends meta-library-dev with .NET tooling, API design guidelines, and NuGet ecosystem practices.

This Skill Extends

  • meta-library-dev - Foundational library patterns (API design, versioning, testing strategies)

For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.

This Skill Adds

  • .NET tooling: .csproj configuration, dotnet CLI, NuGet packaging
  • .NET idioms: API design guidelines, strong naming, XML documentation
  • .NET ecosystem: NuGet publishing, multi-targeting, source generators

This Skill Does NOT Cover

  • General library patterns - see meta-library-dev
  • ASP.NET Core - see lang-csharp-aspnet-dev
  • Entity Framework - see lang-csharp-ef-dev
  • Desktop application development

Overview

Publishing a .NET library requires understanding the modern .NET SDK project system and NuGet ecosystem:

┌─────────────────────────────────────────────────────────────────┐
│                    .NET Library Stack                           │
├─────────────────────────────────────────────────────────────────┤
│  Source Code (*.cs)                                             │
│       │                                                         │
│       ▼                                                         │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │  .csproj    │───▶│  C# Compiler│───▶│   Assembly  │         │
│  │   Config    │    │   (Roslyn)  │    │    (.dll)   │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
│       │                   │                   │                 │
│       │                   ▼                   │                 │
│       │            ┌─────────────┐            │                 │
│       │            │ XML Docs    │            │                 │
│       │            │   (.xml)    │            │                 │
│       │            └─────────────┘            │                 │
│       │                   │                   │                 │
│       ▼                   ▼                   ▼                 │
│  ┌─────────────────────────────────────────────────────┐       │
│  │                  NuGet Package                       │       │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐             │       │
│  │  │  .dll   │  │  .xml   │  │ .nupkg  │             │       │
│  │  │ Assembly│  │  Docs   │  │Metadata │             │       │
│  │  └─────────┘  └─────────┘  └─────────┘             │       │
│  └─────────────────────────────────────────────────────┘       │
│                          │                                      │
│                          ▼                                      │
│                    ┌───────────┐                                │
│                    │  NuGet    │                                │
│                    │  Publish  │                                │
│                    └───────────┘                                │
└─────────────────────────────────────────────────────────────────┘

Key Decision Points:

| Decision | Options | Recommendation | |----------|---------|----------------| | Target framework | .NET 6+, .NET Standard 2.0, .NET Framework 4.x | .NET 8+ for new libraries; multi-target if broad compatibility needed | | Testing framework | xUnit, NUnit, MSTest | xUnit for new projects; NUnit for compatibility | | Documentation | XML comments, DocFX, Sandcastle | XML comments required; DocFX for rich docs | | Strong naming | Signed, Unsigned | Sign only if required by consumers |


Quick Reference

| Task | Command | |------|---------| | New class library | dotnet new classlib -n MyLibrary | | Build | dotnet build | | Test | dotnet test | | Pack | dotnet pack | | Publish to NuGet | dotnet nuget push *.nupkg -s nuget.org -k <key> | | Multi-target build | dotnet build -f net8.0 | | Generate docs | dotnet build -p:GenerateDocumentationFile=true |


Project File Structure (.csproj)

Required Fields for NuGet Publishing

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- Target framework(s) -->
    <TargetFramework>net8.0</TargetFramework>
    <!-- OR multi-targeting -->
    <!-- <TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks> -->

    <!-- Language version -->
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

    <!-- NuGet package metadata -->
    <PackageId>MyCompany.MyLibrary</PackageId>
    <Version>1.0.0</Version>
    <Authors>Author Name</Authors>
    <Company>Company Name</Company>
    <Description>Brief description of the library</Description>
    <Copyright>Copyright (c) 2025 Company Name</Copyright>

    <!-- Required metadata -->
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <!-- OR use license file -->
    <!-- <PackageLicenseFile>LICENSE.txt</PackageLicenseFile> -->

    <PackageProjectUrl>https://github.com/username/repo</PackageProjectUrl>
    <RepositoryUrl>https://github.com/username/repo</RepositoryUrl>
    <RepositoryType>git</RepositoryType>

    <!-- Recommended -->
    <PackageReadmeFile>README.md</PackageReadmeFile>
    <PackageTags>tag1;tag2;tag3</PackageTags>
    <PackageIcon>icon.png</PackageIcon>
    <PackageReleaseNotes>Release notes for this version</PackageReleaseNotes>

    <!-- Generate XML documentation -->
    <GenerateDocumentationFile>true</GenerateDocumentationFile>

    <!-- Deterministic builds for reproducibility -->
    <Deterministic>true</Deterministic>
    <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>

    <!-- Source link for debugging -->
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  </PropertyGroup>

  <!-- Include README and icon in package -->
  <ItemGroup>
    <None Include="README.md" Pack="true" PackagePath="\" />
    <None Include="icon.png" Pack="true" PackagePath="\" />
  </ItemGroup>

  <!-- Source link dependency -->
  <ItemGroup>
    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
  </ItemGroup>
</Project>

Multi-Targeting Configuration

<PropertyGroup>
  <TargetFrameworks>net8.0;net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>

<!-- Conditional compilation -->
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
  <LangVersion>9.0</LangVersion>
</PropertyGroup>

<!-- Conditional dependencies -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
  <PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>

Strong Naming

<PropertyGroup>
  <SignAssembly>true</SignAssembly>
  <AssemblyOriginatorKeyFile>MyLibrary.snk</AssemblyOriginatorKeyFile>
  <PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
</PropertyGroup>

API Design Guidelines

Namespace Organization

// Good: Clear namespace hierarchy
namespace MyCompany.MyLibrary
{
    // Core types
}

namespace MyCompany.MyLibrary.Extensions
{
    // Extension methods
}

namespace MyCompany.MyLibrary.Abstractions
{
    // Interfaces and abstract classes
}

namespace MyCompany.MyLibrary.Internal
{
    // Internal implementation details
}

Class Design Patterns

Immutable Value Types

/// <summary>
/// Represents a user identifier.
/// </summary>
public readonly record struct UserId
{
    public UserId(Guid value)
    {
        if (value == Guid.Empty)
            throw new ArgumentException("User ID cannot be empty.", nameof(value));

        Value = value;
    }

    public Guid Value { get; }

    public override string ToString() => Value.ToString();
}

Builder Pattern

/// <summary>
/// Builds configuration instances.
/// </summary>
public sealed class ConfigurationBuilder
{
    private TimeSpan _timeout = TimeSpan.FromSeconds(30);
    private int _retryCount = 3;
    private bool _strictMode;

    public ConfigurationBuilder WithTimeout(TimeSpan timeout)
    {
        ArgumentOutOfRangeException.ThrowIfLessThan(timeout, TimeSpan.Zero);
        _timeout = timeout;
        return this;
    }

    public ConfigurationBuilder WithRetryCount(int count)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(count);
        _retryCount = count;
        return this;
    }

    public ConfigurationBuilder EnableStrictMode()
    {
        _strictMode = true;
        return this;
    }

    public Configuration Build()
    {
        return new Configuration(_timeout, _retryCount, _strictMode);
    }
}

/// <summary>
/// Configuration options.
/// </summary>
public sealed class Configuration
{
    internal Configuration(TimeSpan timeout, int retryCount, bool strictMode)
    {
        Timeout = timeout;
        RetryCount = retryCount;
        StrictMode = strictMode;
    }

    public TimeSpan Timeout { get; }
    public int RetryCount { get; }
    public bool StrictMode { get; }
}

Factory Pattern

/// <summary>
/// Factory for creating parsers.
/// </summary>
public static class ParserFactory
{
    public static IParser Create(ParserOptions options)
    {
        return options.Mode switch
        {
            ParserMode.Strict => new StrictParser(options),
            ParserMode.Lenient => new LenientParser(options),
            _ => throw new ArgumentOutOfRangeException(nameof(options.Mode))
        };
    }
}

Interface Design

/// <summary>
/// Defines a parser for input data.
/// </summary>
/// <typeparam name="TInput">The input type.</typeparam>
/// <typeparam name="TOutput">The output type.</typeparam>
public interface IParser<in TInput, out TOutput>
{
    /// <summary>
    /// Parses the input and returns the result.
    /// </summary>
    /// <param name="input">The input to parse.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>The parsed output.</returns>
    /// <exception cref="ParseException">Thrown when parsing fails.</exception>
    Task<TOutput> ParseAsync(TInput input, CancellationToken cancellationToken = default);
}

Extension Methods

/// <summary>
/// Extension methods for strings.
/// </summary>
public static class StringExtensions
{
    /// <summary>
    /// Converts the string to snake_case.
    /// </summary>
    /// <param name="value">The string to convert.</param>
    /// <returns>The snake_case string.</returns>
    public static string ToSnakeCase(this string value)
    {
        ArgumentNullException.ThrowIfNull(value);

        // Implementation
        return value;
    }

    /// <summary>
    /// Determines whether the string is null or white space.
    /// </summary>
    public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
    {
        return string.IsNullOrWhiteSpace(value);
    }
}

XML Documentation

Required Documentation Elements

/// <summary>
/// Processes input data and returns the result.
/// </summary>
/// <param name="input">The input data to process.</param>
/// <param name="options">Processing options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the processed output.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="input"/> is null.
/// </exception>
/// <exception cref="ProcessingException">
/// Thrown when processing fails.
/// </exception>
/// <example>
/// <code>
/// var processor = new DataProcessor();
/// var result = await processor.ProcessAsync(data, options);
/// </code>
/// </example>
public async Task<Output> ProcessAsync(
    Input input,
    ProcessingOptions? options = null,
    CancellationToken cancellationToken = default)
{
    ArgumentNullException.ThrowIfNull(input);
    // Implementation
}

Documentation for Properties

/// <summary>
/// Gets or sets the timeout duration.
/// </summary>
/// <value>
/// The timeout duration. Default is 30 seconds.
/// </value>
/// <remarks>
/// Setting this to <see cref="TimeSpan.Zero"/> disables timeout.
/// </remarks>
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);

Documentation Comments Best Practices

  • Always document public APIs
  • Use <see cref=""/> for cross-references
  • Include <example> sections for complex APIs
  • Document exceptions with <exception>
  • Use <remarks> for additional context
  • Include <value> for properties

Testing Patterns

xUnit Test Structure

using Xunit;
using FluentAssertions;

namespace MyLibrary.Tests;

public class ParserTests
{
    [Fact]
    public async Task ParseAsync_ValidInput_ReturnsExpectedOutput()
    {
        // Arrange
        var parser = new Parser();
        var input = "valid input";

        // Act
        var result = await parser.ParseAsync(input);

        // Assert
        result.Should().NotBeNull();
        result.Value.Should().Be("expected");
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public async Task ParseAsync_InvalidInput_ThrowsArgumentException(string input)
    {
        // Arrange
        var parser = new Parser();

        // Act & Assert
        await Assert.ThrowsAsync<ArgumentException>(
            async () => await parser.ParseAsync(input));
    }

    [Fact]
    public void Constructor_NullOptions_ThrowsArgumentNullException()
    {
        // Act & Assert
        var exception = Assert.Throws<ArgumentNullException>(
            () => new Parser(options: null!));

        exception.ParamName.Should().Be("options");
    }
}

NUnit Test Structure

using NUnit.Framework;

namespace MyLibrary.Tests;

[TestFixture]
public class ParserTests
{
    private Parser _parser = null!;

    [SetUp]
    public void SetUp()
    {
        _parser = new Parser();
    }

    [Test]
    public async Task ParseAsync_ValidInput_ReturnsExpectedOutput()
    {
        // Arrange
        var input = "valid input";

        // Act
        var result = await _parser.ParseAsync(input);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Value, Is.EqualTo("expected"));
    }

    [TestCase(null)]
    [TestCase("")]
    [TestCase("   ")]
    public void ParseAsync_InvalidInput_ThrowsArgumentException(string input)
    {
        // Act & Assert
        Assert.ThrowsAsync<ArgumentException>(
            async () => await _parser.ParseAsync(input));
    }

    [TearDown]
    public void TearDown()
    {
        _parser?.Dispose();
    }
}

Test Project Configuration

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <IsPackable>false</IsPackable>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <!-- xUnit -->
    <PackageReference Include="xunit" Version="2.6.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.4" />

    <!-- OR NUnit -->
    <!-- <PackageReference Include="NUnit" Version="4.0.1" /> -->
    <!-- <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> -->

    <!-- Test utilities -->
    <PackageReference Include="FluentAssertions" Version="6.12.0" />
    <PackageReference Include="Moq" Version="4.20.70" />
    <PackageReference Include="Bogus" Version="35.3.0" />

    <!-- Coverage -->
    <PackageReference Include="coverlet.collector" Version="6.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
  </ItemGroup>
</Project>

Source Generators

Creating a Source Generator

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class MySourceGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Register attribute syntax provider
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: static (s, _) => IsSyntaxTargetForGeneration(s),
                transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
            .Where(static m => m is not null);

        // Generate source
        context.RegisterSourceOutput(classDeclarations, Execute);
    }

    private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
    {
        return node is ClassDeclarationSyntax { AttributeLists.Count: > 0 };
    }

    private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(
        GeneratorSyntaxContext context)
    {
        var classDeclaration = (ClassDeclarationSyntax)context.Node;

        // Check for specific attribute
        foreach (var attributeList in classDeclaration.AttributeLists)
        {
            foreach (var attribute in attributeList.Attributes)
            {
                var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol;
                if (symbol?.ContainingType.Name == "MyGeneratorAttribute")
                {
                    return classDeclaration;
                }
            }
        }

        return null;
    }

    private void Execute(SourceProductionContext context, ClassDeclarationSyntax? classDeclaration)
    {
        if (classDeclaration is null)
            return;

        var source = GenerateSource(classDeclaration);
        context.AddSource($"{classDeclaration.Identifier}.g.cs",
            SourceText.From(source, Encoding.UTF8));
    }

    private string GenerateSource(ClassDeclarationSyntax classDeclaration)
    {
        // Generate source code
        return $@"
namespace {GetNamespace(classDeclaration)}
{{
    partial class {classDeclaration.Identifier}
    {{
        // Generated code
    }}
}}";
    }

    private string GetNamespace(ClassDeclarationSyntax classDeclaration)
    {
        // Extract namespace
        return string.Empty;
    }
}

Source Generator Project Configuration

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
  </ItemGroup>
</Project>

Using the Generator

<!-- In consumer project -->
<ItemGroup>
  <ProjectReference Include="..\MyGenerator\MyGenerator.csproj"
                    OutputItemType="Analyzer"
                    ReferenceOutputAssembly="false" />
</ItemGroup>

Versioning and Compatibility

Semantic Versioning

<PropertyGroup>
  <!-- Major.Minor.Patch -->
  <Version>2.1.3</Version>

  <!-- Assembly version (only increment for breaking changes) -->
  <AssemblyVersion>2.0.0.0</AssemblyVersion>

  <!-- File version (increment for every build) -->
  <FileVersion>2.1.3.42</FileVersion>
</PropertyGroup>

Package Version from Git

<PropertyGroup>
  <MinVerTagPrefix>v</MinVerTagPrefix>
  <MinVerDefaultPreReleaseIdentifiers>preview.0</MinVerDefaultPreReleaseIdentifiers>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="MinVer" Version="5.0.0" PrivateAssets="all" />
</ItemGroup>

API Compatibility Analysis

<PropertyGroup>
  <GenerateCompatibilitySuppressionFile>true</GenerateCompatibilitySuppressionFile>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="Microsoft.DotNet.ApiCompat.Task" Version="8.0.100" PrivateAssets="all" />
</ItemGroup>

Publishing to NuGet

Pre-Publish Checklist

  • [ ] All tests pass: dotnet test
  • [ ] Code builds without warnings: dotnet build -warnaserror
  • [ ] XML documentation is generated
  • [ ] Package metadata is complete
  • [ ] README.md is current
  • [ ] CHANGELOG.md is updated
  • [ ] Version number is bumped appropriately
  • [ ] License file is included
  • [ ] Icon is included (64x64 PNG recommended)
  • [ ] Pack succeeds: dotnet pack
  • [ ] Inspect .nupkg contents

Publishing Commands

# Build and pack
dotnet pack -c Release

# Publish to NuGet.org
dotnet nuget push bin/Release/*.nupkg \
  --api-key YOUR_API_KEY \
  --source https://api.nuget.org/v3/index.json

# Publish to GitHub Packages
dotnet nuget push bin/Release/*.nupkg \
  --api-key YOUR_GITHUB_TOKEN \
  --source https://nuget.pkg.github.com/USERNAME/index.json

Package Validation

<PropertyGroup>
  <EnablePackageValidation>true</EnablePackageValidation>
  <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
</PropertyGroup>

Common Dependencies

Serialization

<ItemGroup>
  <PackageReference Include="System.Text.Json" Version="8.0.0" />
  <!-- OR -->
  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

HTTP Client

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
</ItemGroup>

Dependency Injection

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
</ItemGroup>

Logging

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>

Options Pattern

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
</ItemGroup>

Anti-Patterns

1. Exposing Internal Types

// Bad: Exposes implementation detail
public Dictionary<string, List<InternalType>> GetData();

// Good: Return domain type
public DataCollection GetData();

2. Breaking Binary Compatibility

// v1.0.0
public void Process(string input) { }

// v1.1.0 - WRONG! This breaks binary compatibility
public void Process(string input, int count) { }

// v1.1.0 - Correct: Add overload
public void Process(string input) { }
public void Process(string input, int count) { Process(input); /* new logic */ }

3. Missing Nullability Annotations

// Bad: Unclear nullability
public string? GetName() => null;

// Good: Clear contract
[return: NotNullIfNotNull(nameof(defaultName))]
public string? GetName(string? defaultName = null)
{
    return _name ?? defaultName;
}

4. Synchronous API Over Async

// Bad: Blocking async code
public Result Process()
{
    return ProcessAsync().GetAwaiter().GetResult(); // Deadlock risk
}

// Good: Provide both sync and async
public Result Process() { /* sync implementation */ }
public Task<Result> ProcessAsync(CancellationToken ct = default) { /* async implementation */ }

Best Practices

Use Nullable Reference Types

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

Enable All Warnings

<PropertyGroup>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  <WarningLevel>9999</WarningLevel>
  <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

Use EditorConfig

# .editorconfig
root = true

[*.cs]
# Naming conventions
dotnet_naming_rule.interfaces_should_be_prefixed_with_i.severity = warning
dotnet_naming_rule.interfaces_should_be_prefixed_with_i.symbols = interface
dotnet_naming_rule.interfaces_should_be_prefixed_with_i.style = begins_with_i

# Code style
csharp_prefer_braces = true:warning
csharp_using_directive_placement = outside_namespace:warning

Analyzer Packages

<ItemGroup>
  <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" PrivateAssets="all" />
  <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
  <PackageReference Include="Roslynator.Analyzers" Version="4.7.0" PrivateAssets="all" />
</ItemGroup>

References