前置知识: C#

C#12与C#13新特性

4 minIntermediate2026/6/14

最新C#语言特性

概述

C# 语言持续演进,每个版本都带来新的语法特性和改进。C# 12 随 .NET 8 发布,引入了主构造函数、集合表达式等实用特性;C# 13 随 .NET 9 发布,带来了 params 集合、部分属性等增强。了解这些新特性可以帮助你写出更简洁、更高效的代码。

为什么需要关注新特性?新特性通常是为了解决现有语法的痛点而设计的。比如主构造函数减少了依赖注入中的样板代码,集合表达式让数组初始化更简洁。掌握这些特性不仅能提高编码效率,还能让代码更符合现代 C# 风格。

基础概念

主构造函数:将构造函数参数直接放在声明中,整个都可以使用这些参数。

集合表达式:用统一的方括号语法初始化各种集合型,替代了之前多种不同的初始化语法。

内联数组:一种固定大小的结构体数组,用于高性能场景,运行时可以直接内联访问元素。

params 集合params 关键字不再限于数组,可以用于任何集合型。

部分属性似于部分方法,允许属性的声明和实现分离在不同文件中。

快速上手

确保你的项目使用 .NET 8 或更高版本:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <!-- C# 12 默认可用,C# 13 需要 .NET 9 -->
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
</Project>

C# 12 最直观的新特性

// 主构造函数 - 参数直接放在类名后面
public class Person(string name, int age)
{
    public string Name { get; } = name;
    public int Age { get; set; } = age;

    public void Greet() => Console.WriteLine($"我是 {name}{age} 岁");
}

// 集合表达式 - 统一的初始化语法
int[] arr = [1, 2, 3];
List<string> list = ["hello", "world"];
Span<int> span = [10, 20, 30];

详细用法

C# 12 主构造函数

构造函数最常见的用途是简化依赖注入:

// 传统写法
public class UserService
{
    private readonly IUserRepository _repository;
    private readonly ILogger<UserService> _logger;

    public UserService(IUserRepository repository, ILogger<UserService> logger)
    {
        _repository = repository;
        _logger = logger;
    }
}

// 主构造函数写法 - 更简洁
public class UserService(IUserRepository repository, ILogger<UserService> logger)
{
    public User? GetUser(int id)
    {
        logger.LogInformation("获取用户 {Id}", id);
        return repository.GetById(id);
    }
}

构造函数参数可以在整个中使用:

public class Calculator(double initialValue)
{
    // 参数可以在属性初始化中使用
    public double Value { get; set; } = initialValue;

    // 参数可以在方法中使用
    public double Add(double x) => initialValue + x;

    // 如果需要可变性,可以赋值给字段或属性
    public void Reset() => Value = initialValue;
}

构造函数可以与其他构造函数共存:

public class Person(string name, int age)
{
    public string Name { get; } = name;
    public int Age { get; set; } = age;

    // 无参构造函数调用主构造函数
    public Person() : this("未知", 0) { }

    // 只传名字的构造函数
    public Person(string name) : this(name, 18) { }
}

C# 12 集合表达式

集合表达式提供了统一的集合初始化语法:

// 数组
int[] numbers = [1, 2, 3, 4, 5];

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

// Span<T> 和 ReadOnlySpan<T>
Span<double> span = [1.0, 2.0, 3.0];
ReadOnlySpan<int> readOnly = [10, 20, 30];

// HashSet<T>
HashSet<string> set = ["apple", "banana", "cherry"];

// 不可变集合
ImmutableArray<int> immutable = [1, 2, 3];

集合表达式支持展开运算符

int[] a = [1, 2, 3];
int[] b = [4, 5, 6];

// 使用 .. 展开另一个集合
int[] combined = [..a, ..b, 7, 8];
// 结果: [1, 2, 3, 4, 5, 6, 7, 8]

// 展开与混合使用
List<int> result = [0, ..a, 100, ..b, 200];
// 结果: [0, 1, 2, 3, 100, 4, 5, 6, 200]

// 实际应用:合并配置
string[] defaults = ["config.json", "appsettings.json"];
string[] overrides = ["local.json"];
string[] allConfigs = [..defaults, ..overrides];

C# 12 ref readonly 参数

// ref readonly 参数:传入引用但不允许修改
// 适合大型结构体,避免拷贝同时保证安全
public readonly struct BigMatrix
{
    public double M00, M01, M10, M11;
}

// 使用 ref readonly 传入大型结构体
public double Determinant(ref readonly BigMatrix m)
{
    return m.M00 * m.M11 - m.M01 * m.M10;
}

// 调用时使用 ref
var matrix = new BigMatrix { M00 = 1, M01 = 2, M10 = 3, M11 = 4 };
var det = Determinant(ref matrix);

// 也可以传入 in 参数
var det2 = Determinant(in matrix);

C# 12 内联数组

// 内联数组:固定大小的缓冲区,直接存储在结构体中
// 用于高性能场景,避免堆分配
[System.Runtime.CompilerServices.InlineArray(4)]
public struct Buffer4<T>
{
    private T _element0; // 只需声明第一个元素
    // 编译器自动生成其余 3 个元素的存储空间
}

// 使用内联数组
var buffer = new Buffer4<int>();
buffer[0] = 10;
buffer[1] = 20;
buffer[2] = 30;
buffer[3] = 40;

// 可以遍历
foreach (var item in buffer)
{
    Console.WriteLine(item);
}

C# 12 别名任意

// C# 12 之前,using 别名只能用于命名类型
// C# 12 允许为元组、指针、数组等创建别名

using Point = (int X, int Y);           // 元组别名
using Matrix = double[,];               // 二维数组别名
using Callback = delegate*<int, void>;   // 函数指针别名

// 使用别名
Point p = (10, 20);
Console.WriteLine($"X: {p.X}, Y: {p.Y}");

Matrix m = new double[3, 3];

C# 13 params 集合

// C# 13 之前:params 只支持数组
public void Print(params string[] items) { }

// C# 13:params 支持任何集合类型
public void Print(params ReadOnlySpan<string> items)
{
    foreach (var item in items)
    {
        Console.WriteLine(item);
    }
}

// 调用方式不变
Print("hello", "world", "!");

// 也可以使用 List<T>
public void Process(params List<int> numbers)
{
    numbers.Add(999);
}

// 使用 ReadOnlySpan<T> 的 params 避免了数组分配
// 在性能敏感场景中非常有用

C# 13 部分属性

// 部分属性:声明和实现分离
// 通常用于源生成器场景
public partial class ViewModel
{
    // 声明部分:只有签名
    public partial string Name { get; set; }
}

// 实现部分:通常由源生成器生成
public partial class ViewModel
{
    private string _name = "";
    public partial string Name
    {
        get => _name;
        set => _name = value;
    }
}

C# 13 扩展

// C# 13 引入了扩展类型(Extension types)
// 允许为现有类型添加新成员,而不修改原始类型

extension StringExtensions for string
{
    // 为 string 添加方法
    public bool IsNullOrEmpty() => string.IsNullOrEmpty(this);

    // 为 string 添加属性
    public string Reversed => new string(this.Reverse().ToArray());
}

// 使用扩展
string name = "hello";
if (name.IsNullOrEmpty())
{
    Console.WriteLine("空字符串");
}
Console.WriteLine(name.Reversed); // "olleh"

C# 13 锁对象

// C# 13 引入了专用的锁对象,替代 lock 语句
// 提供更好的性能和更安全的锁管理

// 传统写法
private readonly object _lockObj = new();
lock (_lockObj)
{
    // 临界区代码
}

// C# 13 新写法
private readonly Lock _lock = new();
using (_lock.EnterScope())
{
    // 临界区代码
    // 离开 using 块时自动释放锁
}

// Lock 对象的优势:
// 1. 更好的性能(不需要额外的对象分配)
// 2. 支持异步场景
// 3. 可以检测死锁和重入

C# 13 其他改进

// 1. 更好的 ref 安全性
// 方法可以返回对局部变量的安全引用

// 2. 允许在迭代器方法中使用 ref 结构体
// 之前在 async 方法中不能使用 Span<T> 等 ref 结构体
// C# 13 放宽了部分限制

// 3. 隐式索引器访问
// 在对象初始化器中可以使用索引
var dict = new Dictionary<string, int>
{
    ["one"] = 1,
    ["two"] = 2
};

// 4. 方法组自然类型改进
// 方法组的类型推断更加智能
Func<string, bool> predicate = string.IsNullOrEmpty;
// 之前需要显式指定类型参数

常见场景

使用主构造函数简化服务

// 在 ASP.NET Core 中使用主构造函数
public class OrderController(
    IOrderService orderService,
    ILogger<OrderController> logger,
    IConfiguration config) : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetOrder(int id)
    {
        logger.LogInformation("获取订单 {Id}", id);
        var order = await orderService.GetByIdAsync(id);
        return order is not null ? Ok(order) : NotFound();
    }

    [HttpPost]
    public async Task<ActionResult<Order>> CreateOrder(CreateOrderRequest request)
    {
        var maxItems = config.GetValue<int>("MaxOrderItems");
        if (request.Items.Count > maxItems)
            return BadRequest($"订单项不能超过 {maxItems} 个");

        var order = await orderService.CreateAsync(request);
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }
}

使用集合表达式构建数据管道

// 使用集合表达式和展开运算符构建数据管道
public class DataPipeline
{
    private readonly int[] _defaults = [1, 2, 3];
    private readonly List<int> _overrides = [];

    public int[] GetPipeline()
    {
        // 合并默认值、覆盖值和计算值
        var computed = CalculateValues();
        return [.._defaults, .._overrides, ..computed, 99];
    }

    private int[] CalculateValues() => [10, 20, 30];
}

注意事项

主构造函数参数不是成员:主构造函数的参数不是的字段或属性,它们只在体内可用。如果需要在外部访问,必须显式声明属性。

主构造函数参数的可变性:主构造函数参数在方法中是只读的(除非使用 ref),如果需要修改,应赋值给可变字段。

集合表达式的类型推断:集合表达式依赖目标型推断。如果目标型不明确,需要显式指定型:List<int> list = [1, 2, 3]; 而不是 var list = [1, 2, 3];

版本兼容性:使用新特性需要确保运行时环境支持对应的 .NET 版本。C# 12 需要 .NET 8+,C# 13 需要 .NET 9+。

扩展类型尚在演进:C# 13 的扩展型是较新的特性,具体语法和行为可能在后续版本中调整。

进阶用法

构造函数与继承

// 基类使用主构造函数
public class Entity(long id)
{
    public long Id { get; } = id;
}

// 派生类也使用主构造函数,并调用基类主构造函数
public class User(long id, string name, string email) : Entity(id)
{
    public string Name { get; } = name;
    public string Email { get; } = email;
}

// 多层继承
public class AdminUser(long id, string name, string email, string[] permissions)
    : User(id, name, email)
{
    public bool HasPermission(string perm) =>
        permissions.Contains(perm);
}

集合表达式与 LINQ 结合

// 集合表达式与 LINQ 查询结合使用
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 使用集合表达式构建结果
var result = numbers switch
{
    [] => "空集合",
    [var single] => $"只有一个元素: {single}",
    [var first, var second] => $"两个元素: {first}, {second}",
    [.. var all] => $"共 {all.Length} 个元素"
};

// 在模式匹配中使用集合表达式
string Classify(int[] arr) => arr switch
{
    [] => "空",
    [1, ..] => "以1开头",
    [.., 9] => "以9结尾",
    [1, .., 9] => "以1开头以9结尾",
    _ => "其他"
};