Modern C# 12/13/14 language features: primary constructors, records, collection expressions, field keyword, pattern matching, file-scoped namespaces, required members, raw string literals. Trigger: C# features, primary constructor, record, collection expression, pattern matching, field keyword.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
[..] over explicit collection initializersFeature C# Version Min .NET
--------------------------------------------------
File-scoped namespaces C# 10 .NET 6
Global usings C# 10 .NET 6
Required members C# 11 .NET 7
Raw string literals C# 11 .NET 7
List patterns [a, .., z] C# 11 .NET 7
Primary constructors C# 12 .NET 8
Collection expressions [..] C# 12 .NET 8
Inline arrays C# 12 .NET 8
using aliases for generics C# 12 .NET 8
params collections C# 13 .NET 9
Lock object C# 13 .NET 9
field keyword C# 14 .NET 10
Extension types C# 14 .NET 10
// Services -- capture DI parameters directly
public sealed class OrderService(
IOrderRepository repository,
ILogger<OrderService> logger)
{
public async Task<Order?> GetAsync(Guid id, CancellationToken ct)
{
logger.LogInformation("Fetching order {OrderId}", id);
return await repository.FindAsync(id, ct);
}
}
// Records already had primary constructors -- still the best fit for DTOs
public sealed record OrderResponse(Guid Id, string CustomerName, decimal Total);
// Structs
public readonly record struct Money(decimal Amount, string Currency);
// Array, List, Span -- all use [..]
int[] numbers = [1, 2, 3];
List<string> names = ["Alice", "Bob"];
ReadOnlySpan<byte> bytes = [0x00, 0xFF];
// Spread operator
List<string> merged = [..existing, ..additional, "extra"];
// Empty collection (replaces Array.Empty<T>())
int[] empty = [];
// Command message
public sealed record Create{Entity}Command(
string Name,
string Description) : IRequest<Result<Guid>>;
// Value object
public sealed record Address(
string Street,
string City,
string PostalCode,
string Country);
// Record struct for small value types (no heap allocation)
public readonly record struct Coordinate(double Latitude, double Longitude);
// Switch expression with property pattern
string description = order switch
{
{ Status: OrderStatus.Pending, Total: > 1000 } => "Requires approval",
{ Status: OrderStatus.Pending } => "Awaiting processing",
{ Status: OrderStatus.Shipped } => "In transit",
{ Status: OrderStatus.Delivered } => "Complete",
_ => "Unknown"
};
// List patterns (C# 11)
var result = args switch
{
[] => "No arguments",
[var single] => $"One argument: {single}",
[var first, ..] => $"Multiple arguments, first: {first}"
};
// Relational and logical patterns
bool IsValidAge(int age) => age is >= 0 and <= 150;
// Multi-line SQL or JSON without escaping
var sql = """
SELECT o.Id, o.CustomerName, o.Total
FROM Orders o
WHERE o.Status = @Status
ORDER BY o.CreatedAt DESC
""";
// Interpolated raw strings
var json = $$"""
{
"name": "{{name}}",
"total": {{total}}
}
""";
public sealed class {Company}.{Domain}.Models.ProductOptions
{
public required string Name { get; init; }
public required decimal Price { get; init; }
public string? Description { get; init; }
}
// Caller must set required properties
var options = new ProductOptions
{
Name = "Widget", // required
Price = 9.99m // required
// Description is optional
};
// Auto-property with custom validation -- no backing field declaration needed
public string Name
{
get => field;
set => field = value?.Trim() ?? throw new ArgumentNullException(nameof(value));
}
public int Quantity
{
get => field;
set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
}
using OrderList = System.Collections.Generic.List<{Company}.{Domain}.Models.Order>;
using OrderDict = System.Collections.Generic.Dictionary<System.Guid, {Company}.{Domain}.Models.Order>;
// Then use the alias
OrderList orders = [new Order(), new Order()];
// PREFER: file-scoped (saves one level of indentation)
namespace {Company}.{Domain}.Features.Orders;
public sealed class OrderService { }
// AVOID: block-scoped
namespace {Company}.{Domain}.Features.Orders
{
public sealed class OrderService { }
}
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
Using new List<int> { 1, 2, 3 } on C# 12+ | Verbose, ignores collection expressions | [1, 2, 3] |
| Mutable class for DTO | Unintended mutation | sealed record |
| Long if/else chains for type checks | Hard to read, error-prone | Switch expression with patterns |
string.Format() or $"" for multi-line SQL | Escaping issues, poor readability | Raw string literals """ |
field keyword on .NET 8/9 projects | Compilation error | Check <LangVersion> first |
Primary constructors storing to private readonly | Redundant -- captured params are already fields | Use captured parameter directly |
| Block-scoped namespaces | Wastes indentation | File-scoped namespace X; |
== null / != null comparisons | Operator overloading can break intent | is null / is not null |
<LangVersion> in .csproj or Directory.Build.props (preview, latest, 12, 13, 14)<TargetFramework> for net8.0, net9.0, net10.0class Foo( or struct Foo(record keyword usage in model/DTO files= [ in existing coderequired modifier on propertiesDirectory.Build.props or .csproj for <LangVersion>sealed record[..] syntax on C# 12+ projectsif/switch blocks with switch expressionsdotnet formatfield keyword or extension types on .NET 10+ projects| Scenario | Recommendation |
|---|---|
| .NET 8 project | C# 12: primary constructors, collection expressions, records |
| .NET 9 project | C# 13: all above + params collections, lock object |
| .NET 10+ project | C# 14: all above + field keyword, extension types |
| Shared library targeting multiple TFMs | Use lowest common C# version across targets |
| New greenfield project | Use <LangVersion>latest</LangVersion> and latest TFM |