mcpField
D
dotnet-csharp-modern-patterns

dotnet-csharp-modern-patterns

github.com/wshaddix开发86185 次安装35 周前更新
代码仓库

关于 dotnet-csharp-modern-patterns

为 C# 开发者提供适配项目目标框架的现代语言特性指南。按 TFM 和 C# 版本梳理语法用法与限制,并给出多目标框架项目的兼容建议。

主要功能

  1. 01按 TFM 对照 C# 版本
  2. 02记录与主构造函数用法
  3. 03集合表达式与模式匹配示例
  4. 04说明各版本特性限制
  5. 05多目标框架兼容建议

适用场景

  1. 01确认目标框架支持的 C# 语法
  2. 02选择合适的数据类型与语言特性
  3. 03为多目标框架项目处理兼容性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-csharp-modern-patterns

安装后在 Claude Code 中输入/dotnet-csharp-modern-patterns确认已加载。

详细介绍

dotnet-csharp-modern-patterns

根据项目的目标框架调整的现代 C# 语言功能指南。始终先运行 [skill:dotnet-version-detection],以确定 TFM 和 C# 版本。

交叉引用:[skill:dotnet-csharp-coding-standards],了解命名和风格约定;[skill:dotnet-csharp-async-patterns],了解异步专用模式。


快速参考:TFM 对应的 C# 版本

TFMC#主要语言功能
net8.012主构造函数、集合表达式、任意类型别名
net9.013params 集合、Lock 类型、分部属性
net10.014field 关键字、扩展块、未绑定泛型的 nameof
net11.015(预览版)带 with() 参数的集合表达式

记录

在需要基于值而非标识进行相等性比较的场景中,例如不可变数据传输对象、值语义和领域建模,请使用记录。

记录类(引用类型)
// Positional record: concise, immutable, value equality
public record OrderSummary(int OrderId, decimal Total, DateOnly OrderDate);

// With additional members
public record Customer(string Name, string Email)
{
    public string DisplayName => $"{Name} <{Email}>";
}
记录结构体(值类型,C# 10+)
// Positional record struct: value type with value semantics
public readonly record struct Point(double X, double Y);

// Mutable record struct (rare -- prefer readonly)
public record struct MutablePoint(double X, double Y);
何时使用记录而非类
用例优先使用
DTO、API 响应record
领域值对象(Money、Email)readonly record struct
具有标识的实体(User、Order)class
高吞吐量、小型数据readonly record struct
需要继承record(基于类)
非破坏性变更
var updated = order with { Total = order.Total + tax };

主构造函数(C# 12+,net8.0+)

在类或结构体主体中直接捕获构造函数参数。参数在整个类型中均可用,但不是字段或属性——它们是被捕获的状态。

用于服务(DI 注入)
public class OrderService(IOrderRepository repo, ILogger<OrderService> logger)
{
    public async Task<Order> GetAsync(int id)
    {
        logger.LogInformation("Fetching order {OrderId}", id);
        return await repo.GetByIdAsync(id);
    }
}
注意事项
  • 主构造函数参数是可变的捕获变量,而不是 readonly 字段。如果需要不可变性,请在主体中将其赋给 readonly 字段。
  • 如果需要在构造时验证参数,请勿使用主构造函数——应改用带有保护子句的传统构造函数。
  • 对于记录,位置参数会自动成为公共属性。对于类和结构体,它们仍是私有捕获变量。
// Explicit readonly field when immutability matters
public class Config(string connectionString)
{
    private readonly string _connectionString = connectionString
        ?? throw new ArgumentNullException(nameof(connectionString));
}

集合表达式(C# 12+,net8.0+)

使用统一的 [...] 语法创建集合。

// Array
int[] numbers = [1, 2, 3];

// List
List<string> names = ["Alice", "Bob"];

// Span
ReadOnlySpan<byte> bytes = [0x00, 0xFF];

// Spread operator
int[] combined = [..first, ..second, 99];

// Empty collection
List<int> empty = [];
带参数的集合表达式(C# 15 预览版,net11.0+)

指定容量、比较器或其他构造函数参数:

// Capacity hint
List<int> nums = [with(capacity: 1000), ..Generate()];

// Custom comparer
HashSet<string> set = [with(comparer: StringComparer.OrdinalIgnoreCase), "Alice", "bob"];

// Dictionary with comparer
Dictionary<string, int> map = [with(comparer: StringComparer.OrdinalIgnoreCase),
    new("key1", 1), new("key2", 2)];

仅限 net11.0+。 需要 <LangVersion>preview</LangVersion>。请勿用于更早的 TFM。


模式匹配

Switch 表达式(C# 8+)
string GetDiscount(Customer customer) => customer switch
{
    { Tier: "Gold", YearsActive: > 5 } => "30%",
    { Tier: "Gold" } => "20%",
    { Tier: "Silver" } => "10%",
    _ => "0%"
};
列表模式(C# 11+)
bool IsValid(int[] data) => data is [> 0, .., > 0]; // first and last positive

string Describe(int[] values) => values switch
{
    [] => "empty",
    [var single] => $"single: {single}",
    [var first, .., var last] => $"range: {first}..{last}"
};
类型和属性模式
decimal CalculateShipping(object package) => package switch
{
    Letter { Weight: < 50 } => 0.50m,
    Parcel { Weight: var w } when w < 1000 => 5.00m + w * 0.01m,
    Parcel { IsOversized: true } => 25.00m,
    _ => 10.00m
};

required 成员(C# 11+)

强制调用方在构造时通过对象初始化器初始化属性。

public class UserDto
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? Phone { get; init; }
}

// Compiler enforces Name and Email
var user = new UserDto { Name = "Alice", Email = "[email protected]" };

适用于需要反序列化的 DTO(System.Text.Json 在 .NET 8+ 中会遵循 required)。


field 关键字(C# 14,net10.0+)

在属性访问器中直接访问编译器生成的后备字段。

public class TemperatureSensor
{
    public double Reading
    {
        get => field;
        set => field = value >= -273.15
            ? value
            : throw new ArgumentOutOfRangeException(nameof(value));
    }
}

它取代了声明私有字段并搭配自定义逻辑属性的手动模式。如果需要在 setter 中执行验证或转换,又不想单独使用后备字段,请使用此功能。

仅限 net10.0+。 对于更早的 TFM,请使用传统的私有字段。


扩展块(C# 14,net10.0+)

在单个块中为某个类型组合扩展成员。

public static class EnumerableExtensions
{
    extension<T>(IEnumerable<T> source) where T : class
    {
        public IEnumerable<T> WhereNotNull()
            => source.Where(x => x is not null);

        public bool IsEmpty()
            => !source.Any();
    }
}

仅限 net10.0+。 对于更早的 TFM,请使用传统的 static 扩展方法。


任意类型别名(using,C# 12+,net8.0+)

using Point = (double X, double Y);
using UserId = System.Guid;

Point origin = (0, 0);
UserId id = UserId.NewGuid();

适用于元组别名和领域类型别名,无需创建完整类型。


params 集合(C# 13,net9.0+)

现在,params 除数组外还支持其他集合类型,包括 Span<T>、ReadOnlySpan<T> 以及实现特定集合接口的类型。

public void Log(params ReadOnlySpan<string> messages)
{
    foreach (var msg in messages)
        Console.WriteLine(msg);
}

// Callers: compiler may avoid heap allocation with span-based params
Log("hello", "world");

仅限 net9.0+。 在 net8.0 上,params 仅支持数组。


Lock 类型(C# 13,net9.0+)

使用 System.Threading.Lock 代替 object 进行锁定。

private readonly Lock _lock = new();

public void DoWork()
{
    lock (_lock)
    {
        // thread-safe operation
    }
}

Lock 为高级场景提供基于 Scope 的 API,表达能力优于 lock (object)。

仅适用于 net9.0+。 在 net8.0 上,使用 private readonly object _gate = new(); 和 lock (_gate)。


部分属性(C# 13,net9.0+)

部分属性允许源生成器定义属性签名,再由用户实现,反之亦然。

// In generated file
public partial class ViewModel
{
    public partial string Name { get; set; }
}

// In user file
public partial class ViewModel
{
    private string _name = "";
    public partial string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

仅适用于 net9.0+。 有关生成器模式,请参阅 [skill:dotnet-csharp-source-generators]。


对未绑定泛型类型使用 nameof(C# 14,net10.0+)

string name = nameof(List<>);      // "List"
string name2 = nameof(Dictionary<,>); // "Dictionary"

适用于日志记录、诊断和反射场景。

仅适用于 net10.0+。


多目标框架的 Polyfill 指引

面向多个 TFM 时,较新的语言功能可能无法在较旧的目标框架上编译。可采用以下方法:

  1. PolySharp —— 为编译器要求的类型(IsExternalInit、RequiredMemberAttribute 等)提供 Polyfill,使 init、required 和 record 等语言功能可在较旧的 TFM 上使用。
  2. Polyfill —— 为运行时 API 提供 Polyfill(例如,为 netstandard2.0 提供 string.Contains(char))。
  3. 条件编译 —— 对无法通过 Polyfill 实现的功能使用 #if:
#if NET10_0_OR_GREATER
    // Use field keyword
    public double Value { get => field; set => field = Math.Max(0, value); }
#else
    private double _value;
    public double Value { get => _value; set => _value = Math.Max(0, value); }
#endif

有关 Polyfill 的完整指引,请参阅 [skill:dotnet-multi-targeting]。


知识来源

此技能中的功能指引基于以下公开的语言设计理由:

  • C# 语言设计说明(Mads Torgersen 等) —— 各个 C# 版本功能背后的设计决策。与此技能相关的关键设计理由包括:主构造函数(减少 DI 密集型服务中的样板代码)、集合表达式(统一集合初始化语法)、field 关键字(消除后备字段的繁琐写法)以及扩展块(按目标类型对扩展进行分组)。每项功能都在表达能力与安全性之间取得平衡——例如,主构造函数参数有意设计为可变捕获(而非只读),以保持功能简单;需要不可变性时,请使用显式的只读字段。来源:https://github.com/dotnet/csharplang/tree/main/meetings
  • C# 语言提案仓库 —— 已接受和拟议功能的详细规范及设计理由。来源:https://github.com/dotnet/csharplang/tree/main/proposals

注意: 此技能采用公开记载的设计理由,并不代表或代言上述来源。

参考资料

评论

支持 Markdown