Agents lang-dotnet-library-dev
.NET/C#-specific library development patterns. Use when creating NuGet packages, configuring .csproj for libraries, implementing multi-targeting, designing public APIs, generating XML documentation, publishing to NuGet, or applying .NET library best practices. Extends meta-library-dev with .NET tooling and ecosystem patterns.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/lang-dotnet-library-dev" ~/.claude/skills/arustydev-agents-lang-dotnet-library-dev && rm -rf "$T"
content/skills/lang-dotnet-library-dev/SKILL.md.NET Library Development
.NET/C#-specific patterns for library development and NuGet package publishing. This skill extends
meta-library-dev with .NET tooling, package configuration, and ecosystem practices.
This Skill Extends
- Foundational library patterns (API design, versioning, testing strategies)meta-library-dev
For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.
This Skill Adds
- .NET tooling: .csproj configuration, SDK-style projects, build targets
- NuGet packaging: Package metadata, assets, multi-targeting, SourceLink
- .NET ecosystem: Publishing workflow, strong naming, API analyzers, nullable reference types
This Skill Does NOT Cover
- General library patterns - see
meta-library-dev - F# library development - see
lang-fsharp-dev - ASP.NET web development - see web development skills
- Desktop application development
Overview
Publishing a .NET library requires careful configuration of multiple interconnected systems:
┌─────────────────────────────────────────────────────────────────┐ │ .NET Library Stack │ ├─────────────────────────────────────────────────────────────────┤ │ Source Code (src/) │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ .csproj │───▶│ MSBuild │───▶│ .NET │ │ │ │ config │ │ Engine │ │ Compiler │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ Assembly │ │ XML │ │ │ │ │ (.dll) │ │ Docs │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ NuGet Package │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ .nuspec│ │ lib/ │ │ build/ │ │ │ │ │ │ metadata│ │ targets │ │ props │ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────┐ │ │ │ NuGet │ │ │ │ publish │ │ │ └───────────┘ │ └─────────────────────────────────────────────────────────────────┘
Key Decision Points:
| Decision | Options | Recommendation |
|---|---|---|
| Target frameworks | .NET 6+, .NET Standard, .NET Framework | .NET 6+ for new libs; multi-target for compatibility |
| Package format | PackageReference, packages.config | PackageReference (SDK-style) |
| Strong naming | Signed, Unsigned | Sign if required by consumers; otherwise skip |
| API surface | Minimal, Comprehensive | Start minimal; expand based on feedback |
Quick Reference
| Task | Command |
|---|---|
| New library project | |
| Build | |
| Test | |
| Pack | |
| Pack (Release) | |
| Publish to NuGet | |
| Install locally | |
| List targets | |
Project File Structure
Minimal SDK-Style .csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- Target Framework --> <TargetFramework>net8.0</TargetFramework> <!-- Language Features --> <LangVersion>latest</LangVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <!-- Package Metadata (Required for NuGet) --> <PackageId>MyCompany.MyLibrary</PackageId> <Version>1.0.0</Version> <Authors>Your Name</Authors> <Company>Your Company</Company> <Description>A brief description of what this library does</Description> <PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageProjectUrl>https://github.com/username/repo</PackageProjectUrl> <RepositoryUrl>https://github.com/username/repo</RepositoryUrl> <RepositoryType>git</RepositoryType> <PackageTags>tag1;tag2;tag3</PackageTags> <!-- Documentation --> <GenerateDocumentationFile>true</GenerateDocumentationFile> <PublishRepositoryUrl>true</PublishRepositoryUrl> <EmbedUntrackedSources>true</EmbedUntrackedSources> <IncludeSymbols>true</IncludeSymbols> <SymbolPackageFormat>snupkg</SymbolPackageFormat> </PropertyGroup> <!-- SourceLink for debugging --> <ItemGroup> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> </ItemGroup> </Project>
Multi-Targeting
<PropertyGroup> <!-- Multiple target frameworks --> <TargetFrameworks>net8.0;net6.0;netstandard2.0</TargetFrameworks> </PropertyGroup> <!-- Conditional dependencies based on target framework --> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <PackageReference Include="System.Memory" Version="4.5.5" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'"> <!-- .NET 6+ has this built-in --> </ItemGroup>
Package Metadata Best Practices
<PropertyGroup> <!-- Versioning --> <Version>1.0.0</Version> <AssemblyVersion>1.0.0.0</AssemblyVersion> <FileVersion>1.0.0.0</FileVersion> <!-- Package Info --> <PackageId>MyCompany.MyLibrary</PackageId> <Title>My Library</Title> <Description> Detailed description of the library. Can span multiple lines. </Description> <Summary>Short one-line summary</Summary> <PackageReleaseNotes> 1.0.0: - Initial release - Feature A - Feature B </PackageReleaseNotes> <!-- Legal --> <PackageLicenseExpression>MIT</PackageLicenseExpression> <!-- Or use license file --> <!-- <PackageLicenseFile>LICENSE.txt</PackageLicenseFile> --> <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> <Copyright>Copyright (c) 2025 Your Company</Copyright> <!-- Links --> <PackageProjectUrl>https://yourproject.com</PackageProjectUrl> <PackageReadmeFile>README.md</PackageReadmeFile> <PackageIcon>icon.png</PackageIcon> <RepositoryUrl>https://github.com/username/repo</RepositoryUrl> <RepositoryType>git</RepositoryType> <!-- Tags (semicolon-separated) --> <PackageTags>library;utilities;dotnet</PackageTags> </PropertyGroup> <!-- Include README and icon --> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="/" /> <None Include="icon.png" Pack="true" PackagePath="/" /> </ItemGroup>
Target Framework Selection
Framework Decision Matrix
| Target Framework | Use When | Compatibility |
|---|---|---|
| Modern apps, latest features | .NET 8+ only |
| LTS support, wide compatibility | .NET 6+ |
| Maximum compatibility | .NET Framework 4.6.1+, .NET Core 2.0+, Mono, Xamarin |
| Modern features, no .NET Framework | .NET Core 3.0+, Mono 6.4+, Xamarin |
| .NET Framework specific features | .NET Framework 4.8 only |
Multi-Targeting Strategy
<PropertyGroup> <!-- Recommended: Cover modern + legacy --> <TargetFrameworks>net8.0;net6.0;netstandard2.0</TargetFrameworks> </PropertyGroup> <!-- Use preprocessor directives for platform-specific code -->
#if NET8_0_OR_GREATER // Use .NET 8 specific features public async Task<T> ProcessAsync<T>(CancellationToken ct = default) { await Task.CompletedTask; return default!; } #elif NETSTANDARD2_0 // Fallback for .NET Standard public Task<T> ProcessAsync<T>() { return Task.FromResult(default(T)); } #endif
Common Preprocessor Symbols
| Symbol | Target Framework |
|---|---|
| .NET 8.0 |
| .NET 6.0 |
| .NET Standard 2.0 |
| .NET Standard 2.1 |
| .NET 8.0 and later |
| Any .NET Core/.NET 5+ |
| Any .NET Framework |
Public API Design
Namespace Organization
// Root namespace matches package ID namespace MyCompany.MyLibrary { // Core types in root namespace public class Client { } public interface IClientFactory { } } namespace MyCompany.MyLibrary.Models { // Data models in dedicated namespace public class Request { } public class Response { } } namespace MyCompany.MyLibrary.Extensions { // Extension methods in dedicated namespace public static class StringExtensions { } } namespace MyCompany.MyLibrary.Internal { // Internal helpers (not public API) internal static class Helpers { } }
Access Modifiers Strategy
// Public - Part of stable API public class PublicClass { } // Public types with internal constructors public sealed class Config { // Force use of builder/factory internal Config() { } } // Internal - Implementation details internal class InternalHelper { } // Private protected - Derived classes in same assembly only protected internal class ExtensibleInternal { } // Use InternalsVisibleTo for testing [assembly: InternalsVisibleTo("MyLibrary.Tests")]
Nullable Reference Types
// Enable in .csproj // <Nullable>enable</Nullable> // Nullable parameters and returns public string? FindUser(string? searchTerm) { if (string.IsNullOrEmpty(searchTerm)) return null; return "found"; } // Non-nullable parameters public void ProcessData(string data) { // 'data' is guaranteed non-null by compiler Console.WriteLine(data.Length); } // Nullable value types public int? TryParse(string input) { if (int.TryParse(input, out var result)) return result; return null; }
API Design Best Practices
Use abstract classes for base implementations:
public abstract class ClientBase { protected ClientBase(ClientOptions options) { Options = options ?? throw new ArgumentNullException(nameof(options)); } protected ClientOptions Options { get; } public abstract Task<Response> ExecuteAsync(Request request); }
Prefer interfaces for contracts:
public interface IDataStore { Task<T?> GetAsync<T>(string key, CancellationToken ct = default); Task SetAsync<T>(string key, T value, CancellationToken ct = default); Task DeleteAsync(string key, CancellationToken ct = default); }
Use builder pattern for complex configuration:
public class ClientBuilder { private string? _baseUrl; private TimeSpan _timeout = TimeSpan.FromSeconds(30); private IAuthProvider? _authProvider; public ClientBuilder WithBaseUrl(string baseUrl) { _baseUrl = baseUrl; return this; } public ClientBuilder WithTimeout(TimeSpan timeout) { _timeout = timeout; return this; } public ClientBuilder WithAuth(IAuthProvider authProvider) { _authProvider = authProvider; return this; } public Client Build() { if (string.IsNullOrEmpty(_baseUrl)) throw new InvalidOperationException("BaseUrl is required"); return new Client(_baseUrl, _timeout, _authProvider); } }
Provide factory methods:
public class Client { private Client(string baseUrl, TimeSpan timeout, IAuthProvider? auth) { // Private constructor } // Simple factory public static Client Create(string baseUrl) { return new Client(baseUrl, TimeSpan.FromSeconds(30), null); } // Builder factory public static ClientBuilder CreateBuilder() { return new ClientBuilder(); } }
XML Documentation
Documentation Comments
/// <summary> /// Processes the specified data asynchronously. /// </summary> /// <param name="data">The data to process. Cannot be null.</param> /// <param name="options">Optional processing options.</param> /// <param name="cancellationToken">Cancellation token for the operation.</param> /// <returns> /// A task representing the asynchronous operation. The task result contains /// the processed result. /// </returns> /// <exception cref="ArgumentNullException"> /// Thrown when <paramref name="data"/> is null. /// </exception> /// <exception cref="InvalidOperationException"> /// Thrown when the processor is not initialized. /// </exception> /// <example> /// <code> /// var processor = new DataProcessor(); /// var result = await processor.ProcessAsync(myData); /// Console.WriteLine($"Processed: {result}"); /// </code> /// </example> /// <remarks> /// This method uses advanced algorithms to process data efficiently. /// For large datasets, consider using <see cref="ProcessBatchAsync"/>. /// </remarks> /// <seealso cref="ProcessBatchAsync"/> public async Task<Result> ProcessAsync( Data data, ProcessingOptions? options = null, CancellationToken cancellationToken = default) { // Implementation }
Common Documentation Tags
| Tag | Purpose | Example |
|---|---|---|
| Brief description | |
| Parameter description | |
| Return value description | |
| Exceptions thrown | |
| Additional details | |
| Usage example | |
| Reference to type/member | |
| Related members | |
| Generic type parameter | |
Documentation File Generation
<PropertyGroup> <!-- Generate XML documentation --> <GenerateDocumentationFile>true</GenerateDocumentationFile> <!-- Optional: Treat warnings as errors --> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <!-- Or suppress specific warnings --> <NoWarn>CS1591</NoWarn> <!-- Missing XML comment --> </PropertyGroup>
NuGet Package Assets
Package Folder Structure
MyLibrary.1.0.0.nupkg ├── lib/ │ ├── net8.0/ │ │ ├── MyLibrary.dll │ │ └── MyLibrary.xml │ ├── net6.0/ │ │ ├── MyLibrary.dll │ │ └── MyLibrary.xml │ └── netstandard2.0/ │ ├── MyLibrary.dll │ └── MyLibrary.xml ├── build/ │ └── MyLibrary.props ├── buildTransitive/ │ └── MyLibrary.targets ├── README.md ├── icon.png └── MyLibrary.nuspec
Custom Build Props/Targets
MyLibrary.props (imported early):
<Project> <PropertyGroup> <MyLibraryVersion>1.0.0</MyLibraryVersion> </PropertyGroup> </Project>
MyLibrary.targets (imported late):
<Project> <Target Name="MyLibraryCustomTask" BeforeTargets="Build"> <Message Text="Running MyLibrary custom task" Importance="high" /> </Target> </Project>
Include in package:
<ItemGroup> <None Include="build\**" Pack="true" PackagePath="build" /> <None Include="buildTransitive\**" Pack="true" PackagePath="buildTransitive" /> </ItemGroup>
Analyzers and Source Generators
Include analyzer in package:
<PropertyGroup> <IncludeBuildOutput>false</IncludeBuildOutput> <DevelopmentDependency>true</DevelopmentDependency> </PropertyGroup> <ItemGroup> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> </ItemGroup>
Strong Naming and Signing
When to Strong Name
Use strong naming if:
- Consumers require it (e.g., enterprise environments)
- Installing in GAC
- Interop with other signed assemblies
Skip strong naming if:
- Open source library with no enterprise requirements
- Performance is critical (signing adds overhead)
- No explicit consumer requirements
Signing Configuration
<PropertyGroup> <!-- Enable strong naming --> <SignAssembly>true</SignAssembly> <AssemblyOriginatorKeyFile>MyLibrary.snk</AssemblyOriginatorKeyFile> <!-- Delay signing (for open source) --> <DelaySign>false</DelaySign> <!-- Public sign (alternative to strong naming) --> <PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign> </PropertyGroup>
Generate Key File
# Generate strong name key dotnet new tool-manifest dotnet tool install --local dotnet-sn dotnet sn -k MyLibrary.snk # Or use .NET Framework SDK sn -k MyLibrary.snk
SourceLink and Debugging
SourceLink Configuration
<PropertyGroup> <!-- Enable SourceLink --> <PublishRepositoryUrl>true</PublishRepositoryUrl> <EmbedUntrackedSources>true</EmbedUntrackedSources> <!-- Generate symbol package --> <IncludeSymbols>true</IncludeSymbols> <SymbolPackageFormat>snupkg</SymbolPackageFormat> <!-- Deterministic builds --> <Deterministic>true</Deterministic> <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild> </PropertyGroup> <ItemGroup> <!-- GitHub --> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> <!-- Or GitLab --> <!-- <PackageReference Include="Microsoft.SourceLink.GitLab" Version="8.0.0" PrivateAssets="All"/> --> <!-- Or Azure DevOps --> <!-- <PackageReference Include="Microsoft.SourceLink.AzureRepos.Git" Version="8.0.0" PrivateAssets="All"/> --> </ItemGroup>
Debugging Enhancements
<PropertyGroup> <!-- Embed all sources in PDB --> <EmbedAllSources>true</EmbedAllSources> <!-- Portable PDB format --> <DebugType>portable</DebugType> <!-- Optimize for debugging --> <DebugSymbols>true</DebugSymbols> </PropertyGroup>
API Analyzers and Compatibility
Enable API Analyzers
<PropertyGroup> <!-- Public API analyzers --> <EnablePackageValidation>true</EnablePackageValidation> <GenerateCompatibilitySuppressionFile>true</GenerateCompatibilitySuppressionFile> <!-- API baseline for breaking change detection --> <ApiCompatEnableRuleAttributesMustMatch>true</ApiCompatEnableRuleAttributesMustMatch> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> </ItemGroup>
Public API Files
PublicAPI.Shipped.txt (locked API surface):
MyCompany.MyLibrary.Client MyCompany.MyLibrary.Client.Client(string! baseUrl) -> void MyCompany.MyLibrary.Client.ExecuteAsync(MyCompany.MyLibrary.Request! request) -> System.Threading.Tasks.Task<MyCompany.MyLibrary.Response!>!
PublicAPI.Unshipped.txt (new APIs not yet released):
MyCompany.MyLibrary.Client.CancelAsync() -> System.Threading.Tasks.Task!
Testing Libraries
Test Project Structure
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> <IsTestProject>true</IsTestProject> </PropertyGroup> <ItemGroup> <!-- Testing frameworks --> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageReference Include="xUnit" Version="2.6.6" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.6" /> <!-- Assertion libraries --> <PackageReference Include="FluentAssertions" Version="6.12.0" /> <!-- Mocking --> <PackageReference Include="Moq" Version="4.20.70" /> <!-- Test coverage --> <PackageReference Include="coverlet.collector" Version="6.0.0" /> </ItemGroup> <ItemGroup> <!-- Reference to library project --> <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" /> </ItemGroup> <!-- Access internals for testing --> <ItemGroup> <InternalsVisibleTo Include="$(AssemblyName)" /> </ItemGroup> </Project>
Common Test Patterns
xUnit tests:
public class ClientTests { [Fact] public async Task ExecuteAsync_ValidRequest_ReturnsResponse() { // Arrange var client = Client.Create("https://api.example.com"); var request = new Request { Data = "test" }; // Act var response = await client.ExecuteAsync(request); // Assert response.Should().NotBeNull(); response.Status.Should().Be(200); } [Theory] [InlineData("")] [InlineData(null)] public void Create_InvalidBaseUrl_ThrowsArgumentException(string baseUrl) { // Act & Assert Action act = () => Client.Create(baseUrl); act.Should().Throw<ArgumentException>(); } }
Testing with mocks:
public class ServiceTests { [Fact] public async Task ProcessAsync_CallsDataStore() { // Arrange var mockStore = new Mock<IDataStore>(); mockStore.Setup(x => x.GetAsync<string>("key", default)) .ReturnsAsync("value"); var service = new Service(mockStore.Object); // Act var result = await service.ProcessAsync("key"); // Assert result.Should().Be("value"); mockStore.Verify(x => x.GetAsync<string>("key", default), Times.Once); } }
Publishing to NuGet
Pre-Publish Checklist
-
succeedsdotnet build -c Release -
passesdotnet test - Version bumped in .csproj
- CHANGELOG.md updated
- README.md is current
- XML documentation complete
-
succeedsdotnet pack -c Release - Package contents verified
- SourceLink configured
- Symbol package generated
Publishing Commands
# Build in Release configuration dotnet build -c Release # Run tests dotnet test # Create NuGet package dotnet pack -c Release -o ./artifacts # Verify package contents dotnet nuget list source # Or use NuGetPackageExplorer # Publish to NuGet.org dotnet nuget push ./artifacts/MyLibrary.1.0.0.nupkg \ --api-key YOUR_API_KEY \ --source https://api.nuget.org/v3/index.json # Publish symbols dotnet nuget push ./artifacts/MyLibrary.1.0.0.snupkg \ --api-key YOUR_API_KEY \ --source https://api.nuget.org/v3/index.json
Local Testing
# Pack the library dotnet pack -c Release # Add local source dotnet nuget add source ./bin/Release --name local # Install from local source dotnet add package MyLibrary --source local # Or test directly dotnet add package MyLibrary --source ./bin/Release
Continuous Deployment
GitHub Actions:
name: Publish to NuGet on: release: types: [published] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build -c Release --no-restore - name: Test run: dotnet test -c Release --no-build - name: Pack run: dotnet pack -c Release --no-build -o ./artifacts - name: Push to NuGet run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
Common Dependencies
Serialization
<ItemGroup> <PackageReference Include="System.Text.Json" Version="8.0.0" /> <!-- Or Newtonsoft.Json for .NET Framework compatibility --> <!-- <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> --> </ItemGroup>
Logging
<ItemGroup> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" /> </ItemGroup>
Configuration
<ItemGroup> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" /> </ItemGroup>
Dependency Injection
<ItemGroup> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" /> </ItemGroup>
Polyfills for .NET Standard
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <PackageReference Include="System.Memory" Version="4.5.5" /> <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" /> </ItemGroup>
Anti-Patterns
1. Missing Async Suffix
// Bad: Doesn't follow async naming convention public Task<Result> Process() // Good: Clear async method name public Task<Result> ProcessAsync(CancellationToken ct = default)
2. Throwing Instead of Returning Result
// Bad: Forces exception handling public User GetUser(int id) { throw new UserNotFoundException(); } // Good: Return nullable or Result type public User? GetUser(int id) { return null; // Or use Result<User, Error> }
3. Mutable Public Collections
// Bad: Callers can modify collection public List<Item> Items { get; set; } // Good: Expose as read-only public IReadOnlyList<Item> Items { get; } // Or return copy public IEnumerable<Item> GetItems() => _items.ToList();
4. Missing CancellationToken
// Bad: No way to cancel long-running operation public Task<Result> ProcessAsync() // Good: Support cancellation public Task<Result> ProcessAsync(CancellationToken cancellationToken = default)
5. Over-Exposing Internals
// Bad: Implementation details in public API public class Client { public HttpClient HttpClient { get; set; } public Dictionary<string, string> InternalCache { get; set; } } // Good: Hide implementation public class Client { private readonly HttpClient _httpClient; private readonly Dictionary<string, string> _cache; }
Troubleshooting
NuGet Pack Issues
Symptom: Package is empty or missing files
Fixes:
<!-- Ensure build output is included --> <PropertyGroup> <IncludeBuildOutput>true</IncludeBuildOutput> </PropertyGroup> <!-- Include additional files --> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="/" /> </ItemGroup>
Multi-Targeting Build Errors
Symptom: Code doesn't compile for all target frameworks
Fix: Use conditional compilation
#if NET8_0_OR_GREATER // Modern implementation #else // Fallback for older frameworks #endif
Missing XML Documentation Warnings
Symptom: CS1591 warnings for missing documentation
Fixes:
<!-- Suppress warnings --> <PropertyGroup> <NoWarn>CS1591</NoWarn> </PropertyGroup> <!-- Or treat as errors and fix --> <PropertyGroup> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup>
SourceLink Not Working
Symptom: "Step Into" doesn't work for library code
Diagnostic:
# Verify SourceLink in package dotnet sourcelink test MyLibrary.1.0.0.nupkg # Check PDB is embedded dotnet tool install -g dotnet-symbol dotnet symbol --symbols MyLibrary.dll
Fix: Ensure CI sets ContinuousIntegrationBuild
<PropertyGroup> <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild> </PropertyGroup>
Project Structure
Recommended Layout
MyLibrary/ ├── src/ │ └── MyLibrary/ │ ├── MyLibrary.csproj │ ├── Client.cs │ ├── Models/ │ │ ├── Request.cs │ │ └── Response.cs │ ├── Extensions/ │ │ └── ServiceCollectionExtensions.cs │ └── Internal/ │ └── Helpers.cs ├── tests/ │ └── MyLibrary.Tests/ │ ├── MyLibrary.Tests.csproj │ ├── ClientTests.cs │ └── Models/ │ └── RequestTests.cs ├── samples/ │ └── MyLibrary.Samples/ │ ├── MyLibrary.Samples.csproj │ └── Program.cs ├── docs/ │ └── getting-started.md ├── .github/ │ └── workflows/ │ └── ci.yml ├── MyLibrary.sln ├── README.md ├── CHANGELOG.md ├── LICENSE └── icon.png
References
- Foundational library patternsmeta-library-dev- .NET Library Guidance
- NuGet Best Practices
- API Design Guidelines
- SourceLink Documentation