Claude-skill-registry lang-dotnet-dev
Foundational .NET patterns covering runtime, project structure, dependency injection, configuration, metaprogramming, and cross-platform development. Use when working with .NET projects or CLI tools. This is the entry point for .NET development.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lang-dotnet-dev" ~/.claude/skills/majiayu000-claude-skill-registry-lang-dotnet-dev && rm -rf "$T"
skills/data/lang-dotnet-dev/SKILL.md.NET Development Skill
Comprehensive foundational patterns for .NET development, covering runtime fundamentals, project structure, dependency injection, configuration management, logging, middleware, metaprogramming, and cross-platform development.
Table of Contents
- .NET Runtime and SDK
- Project Structure
- CLI Tools
- Project Types
- Dependency Injection
- Configuration
- Logging
- Middleware Patterns
- NuGet Packages
- Cross-Platform Development
- Metaprogramming
- Best Practices
- Cross-Cutting Patterns
.NET Runtime and SDK
Runtime Components
The .NET runtime provides the execution environment for .NET applications.
// Runtime information using System; using System.Runtime.InteropServices; public class RuntimeInfo { public static void DisplayRuntimeInfo() { // Framework description Console.WriteLine($"Framework: {RuntimeInformation.FrameworkDescription}"); // OS description Console.WriteLine($"OS: {RuntimeInformation.OSDescription}"); // Architecture Console.WriteLine($"Architecture: {RuntimeInformation.OSArchitecture}"); // Process architecture Console.WriteLine($"Process: {RuntimeInformation.ProcessArchitecture}"); // Runtime identifier Console.WriteLine($"RID: {RuntimeInformation.RuntimeIdentifier}"); } }
SDK vs Runtime
# Check installed SDKs dotnet --list-sdks # Check installed runtimes dotnet --list-runtimes # Check SDK version dotnet --version # Display all info dotnet --info
Global.json Configuration
{ "sdk": { "version": "8.0.0", "rollForward": "latestMinor", "allowPrerelease": false } }
# Create global.json with specific SDK version dotnet new globaljson --sdk-version 8.0.100 # Roll forward policies: # - patch: Use latest patch version # - feature: Use latest feature band # - minor: Use latest minor version # - major: Use latest major version # - latestPatch: Use latest patch (default) # - latestMinor: Use latest minor # - latestMajor: Use latest major # - disable: Use exact version
.NET Standard vs .NET
// Target Framework Monikers (TFM) // Modern .NET (cross-platform) // <TargetFramework>net8.0</TargetFramework> // <TargetFramework>net7.0</TargetFramework> // <TargetFramework>net6.0</TargetFramework> // .NET Standard (compatibility layer) // <TargetFramework>netstandard2.1</TargetFramework> // <TargetFramework>netstandard2.0</TargetFramework> // .NET Framework (Windows only) // <TargetFramework>net48</TargetFramework> // <TargetFramework>net472</TargetFramework> // Multi-targeting // <TargetFrameworks>net8.0;net6.0;netstandard2.0</TargetFrameworks> // Conditional compilation #if NET8_0_OR_GREATER // Code for .NET 8+ #elif NET6_0_OR_GREATER // Code for .NET 6+ #elif NETSTANDARD2_0 // Code for .NET Standard 2.0 #endif
Runtime Configuration
// runtimeconfig.json { "runtimeOptions": { "tfm": "net8.0", "framework": { "name": "Microsoft.NETCore.App", "version": "8.0.0" }, "configProperties": { "System.GC.Server": true, "System.GC.Concurrent": true, "System.GC.RetainVM": true, "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false } } }
Project Structure
Project File (.csproj)
<Project Sdk="Microsoft.NET.Sdk"> <!-- Basic Properties --> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <OutputType>Exe</OutputType> <RootNamespace>MyCompany.MyProduct</RootNamespace> <AssemblyName>MyProduct</AssemblyName> <LangVersion>latest</LangVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <!-- Package Properties --> <PropertyGroup> <PackageId>MyCompany.MyProduct</PackageId> <Version>1.0.0</Version> <Authors>Your Name</Authors> <Company>MyCompany</Company> <Product>MyProduct</Product> <Description>Package description</Description> <Copyright>Copyright © 2024</Copyright> <PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageProjectUrl>https://github.com/mycompany/myproduct</PackageProjectUrl> <RepositoryUrl>https://github.com/mycompany/myproduct</RepositoryUrl> <RepositoryType>git</RepositoryType> <PackageTags>tag1;tag2;tag3</PackageTags> <GeneratePackageOnBuild>false</GeneratePackageOnBuild> </PropertyGroup> <!-- Build Properties --> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <WarningsAsErrors /> <NoWarn>CS1591</NoWarn> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> <!-- Debug Configuration --> <PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <DebugType>full</DebugType> <DebugSymbols>true</DebugSymbols> <Optimize>false</Optimize> <DefineConstants>DEBUG;TRACE</DefineConstants> </PropertyGroup> <!-- Release Configuration --> <PropertyGroup Condition="'$(Configuration)' == 'Release'"> <DebugType>pdbonly</DebugType> <DebugSymbols>true</DebugSymbols> <Optimize>true</Optimize> <DefineConstants>TRACE</DefineConstants> </PropertyGroup> <!-- Package References --> <ItemGroup> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> </ItemGroup> <!-- Project References --> <ItemGroup> <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" /> </ItemGroup> <!-- Conditional Package References --> <ItemGroup Condition="'$(TargetFramework)' == 'net8.0'"> <PackageReference Include="System.Text.Json" Version="8.0.0" /> </ItemGroup> <!-- Content Files --> <ItemGroup> <Content Include="appsettings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <Content Include="appsettings.*.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <DependentUpon>appsettings.json</DependentUpon> </Content> </ItemGroup> <!-- Embedded Resources --> <ItemGroup> <EmbeddedResource Include="Resources\*.txt" /> </ItemGroup> <!-- None Items --> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="\" /> <None Include="LICENSE" Pack="true" PackagePath="\" /> </ItemGroup> </Project>
Solution File (.sln)
# Create a new solution dotnet new sln -n MySolution # Add projects to solution dotnet sln add src/MyApp/MyApp.csproj dotnet sln add src/MyLibrary/MyLibrary.csproj dotnet sln add tests/MyApp.Tests/MyApp.Tests.csproj # List projects in solution dotnet sln list # Remove project from solution dotnet sln remove src/OldProject/OldProject.csproj
Directory Structure
MySolution/ ├── .gitignore ├── .editorconfig ├── global.json ├── MySolution.sln ├── Directory.Build.props # Shared properties for all projects ├── Directory.Build.targets # Shared targets for all projects ├── Directory.Packages.props # Central package management ├── src/ │ ├── MyApp/ │ │ ├── MyApp.csproj │ │ ├── Program.cs │ │ ├── appsettings.json │ │ ├── appsettings.Development.json │ │ └── Controllers/ │ │ └── WeatherController.cs │ └── MyLibrary/ │ ├── MyLibrary.csproj │ ├── Class1.cs │ └── Interfaces/ │ └── IMyService.cs ├── tests/ │ ├── MyApp.Tests/ │ │ ├── MyApp.Tests.csproj │ │ └── UnitTest1.cs │ └── MyLibrary.Tests/ │ ├── MyLibrary.Tests.csproj │ └── Class1Tests.cs ├── docs/ │ └── README.md └── tools/ └── build.ps1
Directory.Build.props
<Project> <!-- Common properties for all projects --> <PropertyGroup> <LangVersion>latest</LangVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> <!-- Version properties --> <PropertyGroup> <VersionPrefix>1.0.0</VersionPrefix> <VersionSuffix Condition="'$(Configuration)' == 'Debug'">dev</VersionSuffix> </PropertyGroup> <!-- Source Link (for debugging) --> <PropertyGroup> <PublishRepositoryUrl>true</PublishRepositoryUrl> <EmbedUntrackedSources>true</EmbedUntrackedSources> <IncludeSymbols>true</IncludeSymbols> <SymbolPackageFormat>snupkg</SymbolPackageFormat> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" /> </ItemGroup> </Project>
Directory.Packages.props (Central Package Management)
<Project> <PropertyGroup> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> </PropertyGroup> <ItemGroup> <!-- Common packages --> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" /> <!-- Test packages --> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageVersion Include="xunit" Version="2.6.2" /> <PackageVersion Include="xunit.runner.visualstudio" Version="2.5.4" /> <PackageVersion Include="coverlet.collector" Version="6.0.0" /> </ItemGroup> </Project>
CLI Tools
dotnet new
# List available templates dotnet new list # Common templates dotnet new console -n MyConsoleApp dotnet new classlib -n MyLibrary dotnet new web -n MyWebApp dotnet new webapi -n MyApi dotnet new mvc -n MyMvcApp dotnet new razor -n MyRazorApp dotnet new blazorserver -n MyBlazorApp dotnet new blazorwasm -n MyBlazorWasmApp dotnet new worker -n MyWorkerService dotnet new xunit -n MyTests dotnet new nunit -n MyNUnitTests dotnet new mstest -n MyMSTests # Create with options dotnet new console -n MyApp -f net8.0 -o src/MyApp dotnet new webapi -n MyApi --use-controllers --auth Individual # Install templates dotnet new install Amazon.Lambda.Templates dotnet new install Microsoft.AspNetCore.SpaTemplates # Uninstall templates dotnet new uninstall Amazon.Lambda.Templates # Create from template with language dotnet new console -n MyApp -lang "F#" dotnet new classlib -n MyLib -lang "VB"
dotnet build
# Build project dotnet build # Build with configuration dotnet build -c Release dotnet build --configuration Debug # Build specific framework dotnet build -f net8.0 # Build with no dependencies dotnet build --no-dependencies # Build with no restore dotnet build --no-restore # Build with specific runtime dotnet build -r win-x64 dotnet build -r linux-x64 dotnet build -r osx-x64 dotnet build -r osx-arm64 # Build verbosity dotnet build -v quiet dotnet build -v minimal dotnet build -v normal dotnet build -v detailed dotnet build -v diagnostic # Build and output to directory dotnet build -o ./output
dotnet run
# Run project dotnet run # Run with configuration dotnet run -c Release # Run with framework dotnet run -f net8.0 # Run with arguments dotnet run -- arg1 arg2 dotnet run --project ./src/MyApp/MyApp.csproj # Run with environment dotnet run --environment Production ASPNETCORE_ENVIRONMENT=Production dotnet run # Run without build dotnet run --no-build # Run without restore dotnet run --no-restore # Run with launch profile dotnet run --launch-profile "Production"
dotnet publish
# Publish for deployment dotnet publish # Publish with configuration dotnet publish -c Release # Publish with runtime (self-contained) dotnet publish -r win-x64 --self-contained dotnet publish -r linux-x64 -p:PublishSingleFile=true dotnet publish -r osx-arm64 --self-contained # Publish framework-dependent dotnet publish -r win-x64 --self-contained false # Publish with trimming dotnet publish -c Release -r linux-x64 --self-contained -p:PublishTrimmed=true dotnet publish -c Release -p:PublishTrimmed=true -p:TrimMode=full # Publish as single file dotnet publish -r win-x64 -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfContained=true # Publish ReadyToRun dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true # Publish to folder dotnet publish -o ./publish # Publish with version dotnet publish -p:Version=1.2.3
dotnet restore
# Restore dependencies dotnet restore # Restore with specific source dotnet restore --source https://api.nuget.org/v3/index.json dotnet restore -s ./local-packages # Restore for specific runtime dotnet restore -r win-x64 # Restore with locked mode (use packages.lock.json) dotnet restore --locked-mode # Force evaluation of all dependencies dotnet restore --force # Restore without cache dotnet restore --no-cache
dotnet test
# Run tests dotnet test # Run tests with configuration dotnet test -c Release # Run tests with verbosity dotnet test -v normal dotnet test --verbosity detailed # Run tests with filter dotnet test --filter FullyQualifiedName~MyNamespace dotnet test --filter Category=Unit dotnet test --filter "Priority=1|Category=Smoke" # Run tests with coverage dotnet test --collect:"XPlat Code Coverage" dotnet test /p:CollectCoverage=true # Run tests with logger dotnet test --logger "trx;LogFileName=test-results.trx" dotnet test --logger "html;LogFileName=test-results.html" # Run tests without build dotnet test --no-build
dotnet clean
# Clean build outputs dotnet clean # Clean with configuration dotnet clean -c Release # Clean specific framework dotnet clean -f net8.0 # Clean and remove obj folder dotnet clean rm -rf **/obj
dotnet pack
# Create NuGet package dotnet pack # Pack with configuration dotnet pack -c Release # Pack with version dotnet pack -p:Version=1.2.3 # Pack without build dotnet pack --no-build # Pack to output directory dotnet pack -o ./packages # Include symbols dotnet pack -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
dotnet add/remove
# Add package dotnet add package Microsoft.Extensions.Logging dotnet add package Newtonsoft.Json --version 13.0.3 # Add package from source dotnet add package MyPackage --source ./local-packages # Add package to specific project dotnet add src/MyApp/MyApp.csproj package Serilog # Remove package dotnet remove package Newtonsoft.Json # Add project reference dotnet add reference ../MyLibrary/MyLibrary.csproj # Remove project reference dotnet remove reference ../MyLibrary/MyLibrary.csproj
dotnet list
# List packages dotnet list package dotnet list package --outdated dotnet list package --deprecated dotnet list package --vulnerable # List project references dotnet list reference # List projects in solution dotnet sln list
dotnet tool
# Install global tool dotnet tool install -g dotnet-ef dotnet tool install -g dotnet-format # Install local tool dotnet new tool-manifest dotnet tool install dotnet-ef # List tools dotnet tool list -g dotnet tool list # Update tool dotnet tool update -g dotnet-ef dotnet tool update dotnet-ef # Uninstall tool dotnet tool uninstall -g dotnet-ef dotnet tool uninstall dotnet-ef # Run tool dotnet ef --version dotnet format
Project Types
Console Application
// Program.cs (Top-level statements) using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; var builder = Host.CreateApplicationBuilder(args); // Configure services builder.Services.AddTransient<IMyService, MyService>(); builder.Services.AddLogging(logging => { logging.AddConsole(); logging.AddDebug(); }); var host = builder.Build(); // Run application var service = host.Services.GetRequiredService<IMyService>(); await service.RunAsync(); await host.RunAsync(); // Service implementation public interface IMyService { Task RunAsync(); } public class MyService : IMyService { private readonly ILogger<MyService> _logger; public MyService(ILogger<MyService> logger) { _logger = logger; } public async Task RunAsync() { _logger.LogInformation("Service started at {Time}", DateTime.UtcNow); await Task.Delay(1000); _logger.LogInformation("Service completed at {Time}", DateTime.UtcNow); } }
Class Library
// MyLibrary.csproj /* <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> </Project> */ namespace MyLibrary; /// <summary> /// Calculator service for basic arithmetic operations. /// </summary> public class Calculator { /// <summary> /// Adds two numbers. /// </summary> /// <param name="a">First number</param> /// <param name="b">Second number</param> /// <returns>Sum of a and b</returns> public int Add(int a, int b) => a + b; /// <summary> /// Subtracts two numbers. /// </summary> /// <param name="a">First number</param> /// <param name="b">Second number</param> /// <returns>Difference of a and b</returns> public int Subtract(int a, int b) => a - b; }
Web API
// Program.cs using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); // Add services to the container builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // CORS builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); var app = builder.Build(); // Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseCors(); app.UseAuthorization(); app.MapControllers(); app.Run(); // Controllers/WeatherController.cs using Microsoft.AspNetCore.Mvc; namespace MyApi.Controllers; [ApiController] [Route("api/[controller]")] public class WeatherController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherController> _logger; public WeatherController(ILogger<WeatherController> logger) { _logger = logger; } [HttpGet] public IEnumerable<WeatherForecast> Get() { _logger.LogInformation("Getting weather forecast"); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } [HttpGet("{id}")] public ActionResult<WeatherForecast> GetById(int id) { if (id < 1 || id > 5) return NotFound(); return new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(id)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }; } [HttpPost] public ActionResult<WeatherForecast> Create(WeatherForecast forecast) { _logger.LogInformation("Creating weather forecast"); return CreatedAtAction(nameof(GetById), new { id = 1 }, forecast); } } public record WeatherForecast { public DateOnly Date { get; init; } public int TemperatureC { get; init; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; init; } }
Worker Service
// Program.cs using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.Services.AddHostedService<Worker>(); var host = builder.Build(); await host.RunAsync(); // Worker.cs using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; public Worker(ILogger<Worker> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(10000, stoppingToken); } } public override async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Worker starting"); await base.StartAsync(cancellationToken); } public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Worker stopping"); await base.StopAsync(cancellationToken); } }
Dependency Injection
Service Lifetimes
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = Host.CreateApplicationBuilder(args); // Transient: Created each time requested builder.Services.AddTransient<ITransientService, TransientService>(); // Scoped: Created once per scope (request in web apps) builder.Services.AddScoped<IScopedService, ScopedService>(); // Singleton: Created once for application lifetime builder.Services.AddSingleton<ISingletonService, SingletonService>(); // Singleton instance builder.Services.AddSingleton<IConfigService>(new ConfigService("config")); // Singleton factory builder.Services.AddSingleton<IFactoryService>(sp => { var logger = sp.GetRequiredService<ILogger<FactoryService>>(); return new FactoryService(logger); }); var host = builder.Build();
Service Registration Patterns
using Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionExtensions { // Extension method for clean registration public static IServiceCollection AddMyServices( this IServiceCollection services) { services.AddTransient<IMyService, MyService>(); services.AddScoped<IDataService, DataService>(); services.AddSingleton<ICacheService, CacheService>(); return services; } // Generic registration public static IServiceCollection AddRepository<TEntity>( this IServiceCollection services) where TEntity : class { services.AddScoped<IRepository<TEntity>, Repository<TEntity>>(); return services; } // Conditional registration public static IServiceCollection AddConditionalService( this IServiceCollection services, bool condition) { if (condition) { services.AddTransient<IService, ServiceA>(); } else { services.AddTransient<IService, ServiceB>(); } return services; } // TryAdd - only adds if not already registered public static IServiceCollection AddDefaultServices( this IServiceCollection services) { services.TryAddTransient<IMyService, MyService>(); services.TryAddScoped<IDataService, DataService>(); services.TryAddSingleton<ICacheService, CacheService>(); return services; } // Replace service public static IServiceCollection ReplaceService( this IServiceCollection services) { services.Replace(ServiceDescriptor.Transient<IMyService, NewMyService>()); return services; } // Remove service public static IServiceCollection RemoveService( this IServiceCollection services) { var descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IMyService)); if (descriptor != null) { services.Remove(descriptor); } return services; } }
Constructor Injection
public interface IEmailService { Task SendEmailAsync(string to, string subject, string body); } public interface ILogger<T> { void LogInformation(string message); } public class UserService { private readonly IEmailService _emailService; private readonly ILogger<UserService> _logger; private readonly IConfiguration _configuration; // Constructor injection public UserService( IEmailService emailService, ILogger<UserService> logger, IConfiguration configuration) { _emailService = emailService ?? throw new ArgumentNullException(nameof(emailService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); } public async Task CreateUserAsync(string email, string name) { _logger.LogInformation("Creating user {Name}", name); var welcomeMessage = _configuration["Messages:Welcome"] ?? "Welcome!"; await _emailService.SendEmailAsync(email, "Welcome", welcomeMessage); } }
Service Resolution
using Microsoft.Extensions.DependencyInjection; public class ServiceResolver { private readonly IServiceProvider _serviceProvider; public ServiceResolver(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void ResolveServices() { // Get required service (throws if not found) var requiredService = _serviceProvider.GetRequiredService<IMyService>(); // Get service (returns null if not found) var optionalService = _serviceProvider.GetService<IOptionalService>(); // Get all implementations var allServices = _serviceProvider.GetServices<IMyService>(); // Create scope using var scope = _serviceProvider.CreateScope(); var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>(); // Resolve with keyed services (.NET 8+) var keyedService = _serviceProvider.GetRequiredKeyedService<ICache>("redis"); var anotherKeyedService = _serviceProvider.GetKeyedService<ICache>("memory"); } } // Keyed services (.NET 8+) public static class KeyedServiceExtensions { public static IServiceCollection AddKeyedServices( this IServiceCollection services) { services.AddKeyedSingleton<ICache, RedisCache>("redis"); services.AddKeyedSingleton<ICache, MemoryCache>("memory"); return services; } } public class ServiceUsingKeyedDependencies { private readonly ICache _redisCache; private readonly ICache _memoryCache; public ServiceUsingKeyedDependencies( [FromKeyedServices("redis")] ICache redisCache, [FromKeyedServices("memory")] ICache memoryCache) { _redisCache = redisCache; _memoryCache = memoryCache; } }
Configuration
appsettings.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=mydb;User Id=sa;Password=P@ssw0rd;", "Redis": "localhost:6379" }, "AppSettings": { "ApplicationName": "My Application", "Version": "1.0.0", "Features": { "EnableFeatureA": true, "EnableFeatureB": false } }, "Email": { "SmtpServer": "smtp.example.com", "SmtpPort": 587, "FromAddress": "noreply@example.com", "EnableSsl": true }, "Cache": { "SlidingExpiration": "00:30:00", "AbsoluteExpiration": "01:00:00" }, "ApiClients": { "ExternalApi": { "BaseUrl": "https://api.example.com", "Timeout": "00:00:30", "ApiKey": "" } } }
Environment-Specific Configuration
// appsettings.Development.json { "Logging": { "LogLevel": { "Default": "Debug", "Microsoft.AspNetCore": "Debug" } }, "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=mydb_dev;User Id=dev;Password=dev123;" } } // appsettings.Production.json { "Logging": { "LogLevel": { "Default": "Warning", "Microsoft.AspNetCore": "Error" } } } // appsettings.Staging.json { "Logging": { "LogLevel": { "Default": "Information" } } }
Configuration Options Pattern
// AppSettings.cs public class AppSettings { public const string SectionName = "AppSettings"; public string ApplicationName { get; set; } = string.Empty; public string Version { get; set; } = string.Empty; public FeatureSettings Features { get; set; } = new(); } public class FeatureSettings { public bool EnableFeatureA { get; set; } public bool EnableFeatureB { get; set; } } public class EmailSettings { public const string SectionName = "Email"; public string SmtpServer { get; set; } = string.Empty; public int SmtpPort { get; set; } public string FromAddress { get; set; } = string.Empty; public bool EnableSsl { get; set; } } // Program.cs var builder = WebApplication.CreateBuilder(args); // Bind configuration sections to strongly-typed options builder.Services.Configure<AppSettings>( builder.Configuration.GetSection(AppSettings.SectionName)); builder.Services.Configure<EmailSettings>( builder.Configuration.GetSection(EmailSettings.SectionName)); // Using IOptions<T> public class MyService { private readonly AppSettings _appSettings; private readonly EmailSettings _emailSettings; public MyService( IOptions<AppSettings> appOptions, IOptions<EmailSettings> emailOptions) { _appSettings = appOptions.Value; _emailSettings = emailOptions.Value; } public void DoSomething() { Console.WriteLine(_appSettings.ApplicationName); Console.WriteLine(_emailSettings.SmtpServer); } } // Using IOptionsSnapshot<T> (reloads on change, scoped) public class MyReloadableService { private readonly IOptionsSnapshot<AppSettings> _appSettings; public MyReloadableService(IOptionsSnapshot<AppSettings> appSettings) { _appSettings = appSettings; } public void DoSomething() { // Gets current value (reloaded if config changed) Console.WriteLine(_appSettings.Value.ApplicationName); } } // Using IOptionsMonitor<T> (reloads on change, singleton) public class MyMonitoredService { private readonly IOptionsMonitor<AppSettings> _appSettings; public MyMonitoredService(IOptionsMonitor<AppSettings> appSettings) { _appSettings = appSettings; // Subscribe to changes _appSettings.OnChange(settings => { Console.WriteLine("Configuration changed!"); }); } public void DoSomething() { Console.WriteLine(_appSettings.CurrentValue.ApplicationName); } }
Configuration Sources
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; var builder = Host.CreateApplicationBuilder(args); // Clear default sources builder.Configuration.Sources.Clear(); // Add configuration sources in order (later sources override earlier ones) builder.Configuration .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddXmlFile("config.xml", optional: true, reloadOnChange: true) .AddIniFile("config.ini", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .AddEnvironmentVariables(prefix: "MYAPP_") .AddCommandLine(args) .AddUserSecrets<Program>() // Development only .AddInMemoryCollection(new Dictionary<string, string?> { ["Key1"] = "Value1", ["Key2"] = "Value2" }); var host = builder.Build();
User Secrets (Development)
# Initialize user secrets dotnet user-secrets init # Set secret dotnet user-secrets set "ApiKey" "my-secret-key" dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=mydb;" # List secrets dotnet user-secrets list # Remove secret dotnet user-secrets remove "ApiKey" # Clear all secrets dotnet user-secrets clear
Environment Variables
# Windows (PowerShell) $env:ASPNETCORE_ENVIRONMENT = "Development" $env:ConnectionStrings__DefaultConnection = "Server=localhost;Database=mydb;" $env:AppSettings__ApplicationName = "My App" # Linux/macOS export ASPNETCORE_ENVIRONMENT=Development export ConnectionStrings__DefaultConnection="Server=localhost;Database=mydb;" export AppSettings__ApplicationName="My App" # Note: __ (double underscore) represents nested configuration sections # AppSettings__Features__EnableFeatureA maps to AppSettings:Features:EnableFeatureA
Logging
ILogger Interface
using Microsoft.Extensions.Logging; public class MyService { private readonly ILogger<MyService> _logger; public MyService(ILogger<MyService> logger) { _logger = logger; } public void DoWork() { // Log levels _logger.LogTrace("Trace message"); _logger.LogDebug("Debug message"); _logger.LogInformation("Information message"); _logger.LogWarning("Warning message"); _logger.LogError("Error message"); _logger.LogCritical("Critical message"); // Structured logging with parameters var userId = 123; var userName = "John Doe"; _logger.LogInformation("User {UserId} logged in: {UserName}", userId, userName); // Exception logging try { throw new InvalidOperationException("Something went wrong"); } catch (Exception ex) { _logger.LogError(ex, "An error occurred while processing user {UserId}", userId); } // Log with event ID _logger.LogInformation(new EventId(1001, "UserLogin"), "User {UserId} logged in", userId); // Conditional logging if (_logger.IsEnabled(LogLevel.Debug)) { var expensiveData = GetExpensiveDebugData(); _logger.LogDebug("Debug data: {Data}", expensiveData); } // Log scopes using (_logger.BeginScope("Processing user {UserId}", userId)) { _logger.LogInformation("Starting process"); _logger.LogInformation("Process step 1"); _logger.LogInformation("Process step 2"); _logger.LogInformation("Completed process"); } } private string GetExpensiveDebugData() => "expensive data"; }
Configure Logging
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.Logging.ClearProviders(); // Remove default providers // Add providers builder.Logging.AddConsole(); builder.Logging.AddDebug(); builder.Logging.AddEventSourceLogger(); // Set minimum level builder.Logging.SetMinimumLevel(LogLevel.Information); // Add filtering builder.Logging.AddFilter("Microsoft", LogLevel.Warning); builder.Logging.AddFilter("System", LogLevel.Warning); builder.Logging.AddFilter("MyApp", LogLevel.Debug); // Configure from appsettings.json builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); var host = builder.Build();
Logging Configuration in appsettings.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.EntityFrameworkCore": "Warning", "MyApp": "Debug" }, "Console": { "IncludeScopes": true, "LogLevel": { "Default": "Information" } }, "Debug": { "LogLevel": { "Default": "Debug" } } } }
Third-Party Logging (Serilog Example)
// Install packages: // dotnet add package Serilog.AspNetCore // dotnet add package Serilog.Sinks.Console // dotnet add package Serilog.Sinks.File // dotnet add package Serilog.Sinks.Seq using Serilog; var builder = WebApplication.CreateBuilder(args); // Configure Serilog builder.Host.UseSerilog((context, configuration) => { configuration .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() .Enrich.WithMachineName() .Enrich.WithThreadId() .WriteTo.Console() .WriteTo.File( path: "logs/log-.txt", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 30) .WriteTo.Seq("http://localhost:5341"); }); var app = builder.Build(); // Add Serilog request logging app.UseSerilogRequestLogging(); app.Run();
Middleware Patterns
Built-in Middleware
using Microsoft.AspNetCore.Builder; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // Order matters! Middleware executes in order added // Exception handling (should be first) if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } // HTTPS redirection app.UseHttpsRedirection(); // Static files app.UseStaticFiles(); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(builder.Environment.ContentRootPath, "StaticFiles")), RequestPath = "/files" }); // Routing app.UseRouting(); // CORS (must be after routing, before authorization) app.UseCors(); // Authentication app.UseAuthentication(); // Authorization (must be after authentication) app.UseAuthorization(); // Response caching app.UseResponseCaching(); // Response compression app.UseResponseCompression(); // Session app.UseSession(); // Endpoints (should be last) app.MapControllers(); app.MapRazorPages(); app.Run();
Custom Middleware (Inline)
var app = builder.Build(); // Use method app.Use(async (context, next) => { // Before next middleware Console.WriteLine($"Before: {context.Request.Path}"); await next(context); // After next middleware Console.WriteLine($"After: {context.Response.StatusCode}"); }); // Run method (terminal middleware) app.Run(async context => { await context.Response.WriteAsync("Hello World!"); }); // Map method (branch pipeline) app.Map("/api", apiApp => { apiApp.Use(async (context, next) => { Console.WriteLine("API middleware"); await next(context); }); apiApp.Run(async context => { await context.Response.WriteAsync("API Response"); }); }); // MapWhen method (conditional branch) app.MapWhen( context => context.Request.Query.ContainsKey("branch"), branchApp => { branchApp.Run(async context => { await context.Response.WriteAsync("Branch pipeline"); }); }); app.Run();
Custom Middleware Class
// RequestLoggingMiddleware.cs public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestLoggingMiddleware> _logger; public RequestLoggingMiddleware( RequestDelegate next, ILogger<RequestLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var startTime = DateTime.UtcNow; _logger.LogInformation( "Incoming request: {Method} {Path}", context.Request.Method, context.Request.Path); try { await _next(context); } finally { var elapsed = DateTime.UtcNow - startTime; _logger.LogInformation( "Completed request: {Method} {Path} - Status: {StatusCode} - Duration: {Duration}ms", context.Request.Method, context.Request.Path, context.Response.StatusCode, elapsed.TotalMilliseconds); } } } // Extension method for registration public static class RequestLoggingMiddlewareExtensions { public static IApplicationBuilder UseRequestLogging( this IApplicationBuilder builder) { return builder.UseMiddleware<RequestLoggingMiddleware>(); } } // Usage in Program.cs var app = builder.Build(); app.UseRequestLogging(); app.Run();
Exception Handling Middleware
public class GlobalExceptionHandlerMiddleware { private readonly RequestDelegate _next; private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger; public GlobalExceptionHandlerMiddleware( RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.LogError(ex, "An unhandled exception occurred"); await HandleExceptionAsync(context, ex); } } private static async Task HandleExceptionAsync(HttpContext context, Exception exception) { context.Response.ContentType = "application/json"; context.Response.StatusCode = exception switch { ArgumentException => StatusCodes.Status400BadRequest, UnauthorizedAccessException => StatusCodes.Status401Unauthorized, KeyNotFoundException => StatusCodes.Status404NotFound, _ => StatusCodes.Status500InternalServerError }; var response = new { statusCode = context.Response.StatusCode, message = exception.Message, detailed = exception.ToString() // Only in development! }; await context.Response.WriteAsJsonAsync(response); } }
NuGet Packages
Common Packages
# Core Microsoft packages dotnet add package Microsoft.Extensions.DependencyInjection dotnet add package Microsoft.Extensions.Configuration dotnet add package Microsoft.Extensions.Configuration.Json dotnet add package Microsoft.Extensions.Logging dotnet add package Microsoft.Extensions.Hosting dotnet add package Microsoft.Extensions.Options # ASP.NET Core dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson dotnet add package Swashbuckle.AspNetCore # Entity Framework Core dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Sqlite dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.EntityFrameworkCore.Tools # Testing dotnet add package xunit dotnet add package xunit.runner.visualstudio dotnet add package Microsoft.NET.Test.Sdk dotnet add package Moq dotnet add package FluentAssertions dotnet add package coverlet.collector # Serialization dotnet add package System.Text.Json dotnet add package Newtonsoft.Json # HTTP dotnet add package Microsoft.Extensions.Http dotnet add package Microsoft.Extensions.Http.Polly dotnet add package Polly # Validation dotnet add package FluentValidation dotnet add package FluentValidation.AspNetCore # Mapping dotnet add package AutoMapper dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection # Logging dotnet add package Serilog dotnet add package Serilog.AspNetCore dotnet add package Serilog.Sinks.Console dotnet add package Serilog.Sinks.File # Authentication dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
Package Management
# List installed packages dotnet list package # List outdated packages dotnet list package --outdated # List deprecated packages dotnet list package --deprecated # List vulnerable packages dotnet list package --vulnerable # Update package dotnet add package PackageName --version 2.0.0 # Remove package dotnet remove package PackageName # Restore packages dotnet restore # Clear NuGet cache dotnet nuget locals all --clear
Creating NuGet Packages
<!-- MyLibrary.csproj --> <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <!-- Package metadata --> <PackageId>MyCompany.MyLibrary</PackageId> <Version>1.0.0</Version> <Authors>Your Name</Authors> <Company>MyCompany</Company> <Description>Library description here</Description> <PackageTags>library;utility;helpers</PackageTags> <PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageProjectUrl>https://github.com/mycompany/mylibrary</PackageProjectUrl> <RepositoryUrl>https://github.com/mycompany/mylibrary</RepositoryUrl> <RepositoryType>git</RepositoryType> <PackageReadmeFile>README.md</PackageReadmeFile> <PackageIcon>icon.png</PackageIcon> <!-- Include symbols --> <IncludeSymbols>true</IncludeSymbols> <SymbolPackageFormat>snupkg</SymbolPackageFormat> </PropertyGroup> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="\" /> <None Include="icon.png" Pack="true" PackagePath="\" /> </ItemGroup> </Project>
# Create package dotnet pack -c Release # Create package with version dotnet pack -c Release -p:PackageVersion=1.0.1 # Publish to NuGet.org dotnet nuget push bin/Release/MyLibrary.1.0.0.nupkg --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json # Publish to private feed dotnet nuget push bin/Release/MyLibrary.1.0.0.nupkg --source http://myfeed.example.com/nuget
Cross-Platform Development
Runtime Identifiers (RIDs)
# Common RIDs # Windows win-x64 # Windows 64-bit win-x86 # Windows 32-bit win-arm64 # Windows ARM64 # Linux linux-x64 # Linux 64-bit linux-arm # Linux ARM linux-arm64 # Linux ARM64 linux-musl-x64 # Alpine Linux # macOS osx-x64 # macOS Intel osx-arm64 # macOS Apple Silicon # Publish for specific runtime dotnet publish -r win-x64 --self-contained dotnet publish -r linux-x64 --self-contained dotnet publish -r osx-arm64 --self-contained
Platform-Specific Code
using System.Runtime.InteropServices; public class PlatformService { public string GetPlatformInfo() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return "Running on Windows"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return "Running on Linux"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return "Running on macOS"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) { return "Running on FreeBSD"; } return "Unknown platform"; } public string GetArchitecture() { return RuntimeInformation.OSArchitecture switch { Architecture.X64 => "x64", Architecture.X86 => "x86", Architecture.Arm => "ARM", Architecture.Arm64 => "ARM64", _ => "Unknown" }; } } // Conditional compilation public class PlatformSpecificFeature { public void DoSomething() { #if WINDOWS WindowsSpecificCode(); #elif LINUX LinuxSpecificCode(); #elif OSX MacOSSpecificCode(); #endif } private void WindowsSpecificCode() { } private void LinuxSpecificCode() { } private void MacOSSpecificCode() { } }
Path Handling
using System.IO; public class PathHelper { public void HandlePaths() { // Use Path.Combine for cross-platform paths var configPath = Path.Combine("config", "appsettings.json"); // Get directory separator (\ on Windows, / on Unix) var separator = Path.DirectorySeparatorChar; // Get path separator (; on Windows, : on Unix) var pathSeparator = Path.PathSeparator; // Get temp path var tempPath = Path.GetTempPath(); // Get user profile directory var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); // Get application data directory var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); } }
Metaprogramming
Reflection
Reflection allows runtime inspection and manipulation of types, methods, and properties.
using System; using System.Reflection; public class User { public string Name { get; set; } public int Age { get; set; } public void Greet() => Console.WriteLine($"Hello, I'm {Name}"); } public class ReflectionExamples { public void InspectType() { // Get type information Type type = typeof(User); Type type2 = user.GetType(); Type type3 = Type.GetType("Namespace.User"); // Get type properties Console.WriteLine($"Name: {type.Name}"); Console.WriteLine($"FullName: {type.FullName}"); Console.WriteLine($"Namespace: {type.Namespace}"); Console.WriteLine($"IsClass: {type.IsClass}"); Console.WriteLine($"IsPublic: {type.IsPublic}"); // Get properties foreach (PropertyInfo prop in type.GetProperties()) { Console.WriteLine($"{prop.Name}: {prop.PropertyType}"); } // Get methods foreach (MethodInfo method in type.GetMethods()) { Console.WriteLine($"{method.Name}"); } } public void CreateInstance() { Type type = typeof(User); // Create instance with parameterless constructor object instance = Activator.CreateInstance(type); // Create instance with constructor parameters object instance2 = Activator.CreateInstance( type, new object[] { "Alice", 30 } ); // Generic version User user = Activator.CreateInstance<User>(); } public void AccessMembers() { var user = new User { Name = "Alice", Age = 30 }; Type type = typeof(User); // Get property value PropertyInfo nameProp = type.GetProperty("Name"); string name = (string)nameProp.GetValue(user); // Set property value nameProp.SetValue(user, "Bob"); // Invoke method MethodInfo greetMethod = type.GetMethod("Greet"); greetMethod.Invoke(user, null); // Access private members FieldInfo privateField = type.GetField( "_privateField", BindingFlags.NonPublic | BindingFlags.Instance ); privateField?.SetValue(user, "secret"); } }
Attributes
Attributes provide metadata about code elements that can be queried at runtime or compile time.
using System; // Built-in attributes [Obsolete("Use NewMethod instead")] public void OldMethod() { } [Serializable] public class DataModel { } [AttributeUsage(AttributeTargets.Method)] public class LoggableAttribute : Attribute { public string Message { get; set; } public LogLevel Level { get; set; } public LoggableAttribute(string message = "", LogLevel level = LogLevel.Info) { Message = message; Level = level; } } // Custom attribute with multiple targets [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true )] public class AuthorAttribute : Attribute { public string Name { get; } public string Date { get; set; } public AuthorAttribute(string name) { Name = name; } } // Usage public class MyService { [Loggable("Processing data", LogLevel.Debug)] [Author("Alice", Date = "2024-01-01")] public void ProcessData() { // Method implementation } } // Reading attributes public class AttributeReader { public void ReadMethodAttributes() { var method = typeof(MyService).GetMethod("ProcessData"); // Get single attribute var loggable = method.GetCustomAttribute<LoggableAttribute>(); if (loggable != null) { Console.WriteLine($"Log: {loggable.Message} at {loggable.Level}"); } // Get all attributes of a type var authors = method.GetCustomAttributes<AuthorAttribute>(); foreach (var author in authors) { Console.WriteLine($"Author: {author.Name} on {author.Date}"); } // Check if attribute is present bool isLoggable = method.IsDefined(typeof(LoggableAttribute)); } }
Source Generators (C# 9+)
Source generators create code at compile time, providing compile-time metaprogramming.
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Text; // Source generator [Generator] public class AutoNotifyGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { // Register syntax receiver context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { if (context.SyntaxReceiver is not SyntaxReceiver receiver) return; foreach (var field in receiver.CandidateFields) { var model = context.Compilation.GetSemanticModel(field.SyntaxTree); var fieldSymbol = model.GetDeclaredSymbol(field) as IFieldSymbol; if (fieldSymbol == null) continue; var classSymbol = fieldSymbol.ContainingType; var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var source = GeneratePropertySource( namespaceName, classSymbol.Name, fieldSymbol.Name ); context.AddSource( $"{classSymbol.Name}_{fieldSymbol.Name}_AutoNotify.g.cs", SourceText.From(source, Encoding.UTF8) ); } } private string GeneratePropertySource( string namespaceName, string className, string fieldName) { var propertyName = char.ToUpper(fieldName[1]) + fieldName.Substring(2); return $@" using System.ComponentModel; namespace {namespaceName} {{ public partial class {className} : INotifyPropertyChanged {{ public event PropertyChangedEventHandler? PropertyChanged; public {fieldName.TrimStart('_')} {propertyName} {{ get => {fieldName}; set {{ if ({fieldName} != value) {{ {fieldName} = value; PropertyChanged?.Invoke( this, new PropertyChangedEventArgs(nameof({propertyName})) ); }} }} }} }} }}"; } class SyntaxReceiver : ISyntaxReceiver { public List<FieldDeclarationSyntax> CandidateFields { get; } = new(); public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { if (syntaxNode is FieldDeclarationSyntax fieldDeclaration && fieldDeclaration.AttributeLists.Count > 0) { CandidateFields.Add(fieldDeclaration); } } } } // Usage in consuming code public partial class Person { [AutoNotify] private string _name = ""; // Property is generated by source generator }
Expression Trees
Expression trees represent code as data structures for runtime code generation and analysis.
using System; using System.Linq.Expressions; public class ExpressionTreeExamples { public void BasicExpressions() { // Lambda expression Expression<Func<int, int, int>> add = (a, b) => a + b; // Compile and execute var compiled = add.Compile(); int result = compiled(2, 3); // 5 // Inspect expression tree Console.WriteLine(add.Body); // (a + b) Console.WriteLine(add.Parameters); // [a, b] } public void BuildExpressionTree() { // Build expression: (a, b) => a + b var paramA = Expression.Parameter(typeof(int), "a"); var paramB = Expression.Parameter(typeof(int), "b"); var body = Expression.Add(paramA, paramB); var lambda = Expression.Lambda<Func<int, int, int>>( body, paramA, paramB ); var compiled = lambda.Compile(); int result = compiled(2, 3); // 5 } public void ComplexExpressions() { // Build: user => user.Name == "Alice" && user.Age > 18 var param = Expression.Parameter(typeof(User), "user"); var nameProperty = Expression.Property(param, "Name"); var nameEquals = Expression.Equal( nameProperty, Expression.Constant("Alice") ); var ageProperty = Expression.Property(param, "Age"); var ageGreater = Expression.GreaterThan( ageProperty, Expression.Constant(18) ); var condition = Expression.AndAlso(nameEquals, ageGreater); var lambda = Expression.Lambda<Func<User, bool>>( condition, param ); // Use with LINQ var users = new List<User>(); var filtered = users.AsQueryable().Where(lambda); } public void DynamicPropertyAccess() { // Build property accessor dynamically var param = Expression.Parameter(typeof(User), "user"); var property = Expression.Property(param, "Name"); var lambda = Expression.Lambda<Func<User, string>>(property, param); var accessor = lambda.Compile(); var user = new User { Name = "Alice" }; string name = accessor(user); // "Alice" } }
IL Emit (Reflection.Emit)
Generate IL code dynamically at runtime for maximum performance and flexibility.
using System; using System.Reflection; using System.Reflection.Emit; public class ILEmitExamples { public void CreateDynamicMethod() { // Create dynamic method: int Add(int a, int b) => a + b var method = new DynamicMethod( "Add", typeof(int), new[] { typeof(int), typeof(int) } ); ILGenerator il = method.GetILGenerator(); // IL: ldarg.0 (load first argument) il.Emit(OpCodes.Ldarg_0); // IL: ldarg.1 (load second argument) il.Emit(OpCodes.Ldarg_1); // IL: add (add top two stack values) il.Emit(OpCodes.Add); // IL: ret (return) il.Emit(OpCodes.Ret); // Create delegate var add = (Func<int, int, int>)method.CreateDelegate( typeof(Func<int, int, int>) ); int result = add(2, 3); // 5 } public void CreateDynamicType() { // Create assembly var assemblyName = new AssemblyName("DynamicAssembly"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run ); // Create module var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); // Create type var typeBuilder = moduleBuilder.DefineType( "DynamicType", TypeAttributes.Public ); // Add field var fieldBuilder = typeBuilder.DefineField( "_value", typeof(int), FieldAttributes.Private ); // Add property var propertyBuilder = typeBuilder.DefineProperty( "Value", PropertyAttributes.HasDefault, typeof(int), null ); // Add getter var getterBuilder = typeBuilder.DefineMethod( "get_Value", MethodAttributes.Public | MethodAttributes.SpecialName, typeof(int), Type.EmptyTypes ); var getterIL = getterBuilder.GetILGenerator(); getterIL.Emit(OpCodes.Ldarg_0); getterIL.Emit(OpCodes.Ldfld, fieldBuilder); getterIL.Emit(OpCodes.Ret); propertyBuilder.SetGetMethod(getterBuilder); // Create type Type dynamicType = typeBuilder.CreateType(); object instance = Activator.CreateInstance(dynamicType); } }
Dynamic Language Runtime (DLR)
The DLR provides dynamic typing and late binding for .NET.
using System; using System.Dynamic; public class DynamicExamples { public void UseDynamic() { // Dynamic variables dynamic obj = "Hello"; obj = 42; // OK - type can change obj = new { Name = "Alice" }; // OK - anonymous type Console.WriteLine(obj.Name); // Late binding // Dynamic method calls dynamic calculator = new Calculator(); var result = calculator.Add(2, 3); // Resolved at runtime } public void ExpandoObject() { // Dynamic object with runtime properties dynamic person = new ExpandoObject(); person.Name = "Alice"; person.Age = 30; person.Greet = (Action)(() => Console.WriteLine($"Hello, I'm {person.Name}")); person.Greet(); // Call dynamic method // Can enumerate properties var dict = (IDictionary<string, object>)person; foreach (var kvp in dict) { Console.WriteLine($"{kvp.Key}: {kvp.Value}"); } } } // Custom dynamic object public class DynamicDictionary : DynamicObject { private readonly Dictionary<string, object> _data = new(); public override bool TryGetMember(GetMemberBinder binder, out object result) { return _data.TryGetValue(binder.Name, out result); } public override bool TrySetMember(SetMemberBinder binder, object value) { _data[binder.Name] = value; return true; } public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args, out object result) { if (_data.TryGetValue(binder.Name, out var value) && value is Delegate del) { result = del.DynamicInvoke(args); return true; } result = null; return false; } }
Proxy and Interception Patterns
Use DispatchProxy for dynamic proxy generation.
using System; using System.Reflection; public interface IUserService { User GetUser(int id); void SaveUser(User user); } public class LoggingProxy<T> : DispatchProxy { private T _target; private ILogger _logger; public static T Create(T target, ILogger logger) { var proxy = Create<T, LoggingProxy<T>>() as LoggingProxy<T>; proxy._target = target; proxy._logger = logger; return (T)(object)proxy; } protected override object Invoke(MethodInfo targetMethod, object[] args) { _logger.LogInformation( "Calling {Method} with {Args}", targetMethod.Name, args ); try { var result = targetMethod.Invoke(_target, args); _logger.LogInformation( "Completed {Method} with result {Result}", targetMethod.Name, result ); return result; } catch (Exception ex) { _logger.LogError( ex, "Error in {Method}", targetMethod.Name ); throw; } } } // Usage var userService = new UserService(); var logger = new ConsoleLogger(); var proxy = LoggingProxy<IUserService>.Create(userService, logger); proxy.GetUser(1); // Logged automatically
See also:
patterns-metaprogramming-dev for cross-language metaprogramming patterns
Best Practices
Nullable Reference Types
#nullable enable public class UserService { // Non-nullable reference type private readonly ILogger<UserService> _logger; // Nullable reference type private string? _cachedData; public UserService(ILogger<UserService> logger) { _logger = logger; } // Return nullable public string? FindUser(int id) { return id > 0 ? $"User{id}" : null; } // Parameter nullable public void UpdateCache(string? data) { _cachedData = data; } // Null-forgiving operator public void ProcessData() { var data = GetData(); Console.WriteLine(data!.Length); // I know it's not null! } private string? GetData() => _cachedData; }
Async/Await Patterns
public class AsyncPatterns { // Async method naming (suffix with Async) public async Task<string> GetDataAsync() { await Task.Delay(100); return "data"; } // Avoid async void (except event handlers) public async void HandleClick(object sender, EventArgs e) { await ProcessAsync(); } // ConfigureAwait(false) in libraries public async Task<string> LibraryMethodAsync() { var data = await GetDataAsync().ConfigureAwait(false); return data; } // Parallel async operations public async Task<string[]> GetMultipleAsync() { var task1 = GetDataAsync(); var task2 = GetDataAsync(); var task3 = GetDataAsync(); return await Task.WhenAll(task1, task2, task3); } // Cancellation support public async Task<string> ProcessWithCancellationAsync( CancellationToken cancellationToken) { await Task.Delay(1000, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); return "completed"; } // ValueTask for performance public async ValueTask<int> GetCachedValueAsync(int key) { // Return cached value synchronously if available if (_cache.TryGetValue(key, out var value)) { return value; } // Otherwise fetch asynchronously return await FetchValueAsync(key); } private readonly Dictionary<int, int> _cache = new(); private async Task<int> FetchValueAsync(int key) => await Task.FromResult(key); private async Task ProcessAsync() => await Task.CompletedTask; }
Disposal Patterns
// IDisposable implementation public class ResourceHandler : IDisposable { private bool _disposed; private readonly Stream _stream; public ResourceHandler(Stream stream) { _stream = stream; } public void DoWork() { ObjectDisposedException.ThrowIf(_disposed, this); // Do work } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // Dispose managed resources _stream?.Dispose(); } // Free unmanaged resources _disposed = true; } } // IAsyncDisposable implementation public class AsyncResourceHandler : IAsyncDisposable { private readonly HttpClient _httpClient; public AsyncResourceHandler(HttpClient httpClient) { _httpClient = httpClient; } public async ValueTask DisposeAsync() { // Perform async cleanup _httpClient?.Dispose(); await Task.CompletedTask; } } // Using statement public class ResourceUser { public void UseResource() { using var handler = new ResourceHandler(Stream.Null); handler.DoWork(); } // Dispose called automatically public async Task UseResourceAsync() { await using var handler = new AsyncResourceHandler(new HttpClient()); // Use handler } // DisposeAsync called automatically }
Record Types
// Immutable record public record Person(string FirstName, string LastName, int Age); // Record with additional members public record User(int Id, string Email) { public DateTime CreatedAt { get; init; } = DateTime.UtcNow; public string FullInfo => $"{Id}: {Email} (Created: {CreatedAt})"; } // Record inheritance public record Employee(int Id, string Email, string Department) : User(Id, Email); // With expressions (non-destructive mutation) public class RecordExample { public void Example() { var person = new Person("John", "Doe", 30); var olderPerson = person with { Age = 31 }; // Value equality var person2 = new Person("John", "Doe", 30); Console.WriteLine(person == person2); // True } }
Pattern Matching
public class PatternMatchingExamples { public string Describe(object obj) { return obj switch { null => "null", int i => $"int: {i}", string s => $"string: {s}", Person { Age: >= 18 } => "Adult person", Person { Age: < 18 } p => $"Minor: {p.FirstName}", IEnumerable<int> numbers => $"Number sequence: {numbers.Count()}", _ => "Unknown type" }; } public decimal CalculateDiscount(Customer customer) { return customer switch { { IsPremium: true, YearsOfMembership: > 5 } => 0.20m, { IsPremium: true } => 0.15m, { YearsOfMembership: > 3 } => 0.10m, _ => 0.05m }; } } public record Customer(bool IsPremium, int YearsOfMembership);
Cross-Cutting Patterns
For cross-language comparison and translation patterns, see:
- Reflection, attributes, source generators, IL emit, dynamic typespatterns-metaprogramming-dev
- Async/await, tasks, parallel programming, thread safetypatterns-concurrency-dev
- JSON serialization, validation, configuration bindingpatterns-serialization-dev
This skill provides foundational .NET patterns for modern cross-platform development. These patterns cover the .NET runtime, project structure, CLI tools, dependency injection, configuration management, logging, middleware, metaprogramming, NuGet packages, and best practices for building robust .NET applications.