ワンクリックで
modern-csharp
Use when adopting C# 12/13/14 features — primary constructors, collection expressions, or field keyword.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Use when adopting C# 12/13/14 features — primary constructors, collection expressions, or field keyword.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when writing async code, propagating CancellationTokens, or fixing async/await pitfalls.
Use when applying or enforcing C# coding style — namespaces, sealed classes, var usage, XML docs.
Use when applying modern C# idioms — records, pattern matching, primary constructors, collection expressions.
Use when registering services, choosing lifetimes, or implementing DI patterns like decorator or keyed services.
Use when selecting or implementing design patterns in C# — factory, builder, strategy, decorator, or mediator.
| name | modern-csharp |
| description | Use when adopting C# 12/13/14 features — primary constructors, collection expressions, or field keyword. |
| when_to_use | Use when adopting C# 12/13/14 features — primary constructors, collection expressions, or field keyword. |
| metadata | {"category":"core","agent":"dotnet-architect"} |
[..] 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 | C-Q5: the compiler generates backing storage only when an instance member captures the primary-constructor parameter. Adding an explicit private readonly field allocates a second storage slot and double-writes both — the constructor param is then captured into the implicit field AND copied to your explicit field. Wasted memory + drift risk. | Use the captured parameter directly (e.g., IService _svc; is unnecessary if Foo(IService svc) already captures svc via a method body referencing it). |
| 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 |