C# 12 中的 Primary Constructor

Intro

在即将到来的 C# 12 中,觉得 Primary Constructor 算是比较实用的特性之一了,和大家分享一下

Sample

直接来看几个使用示例吧.

file class Animal(string name) 
{
    public string Name => name;
}

file sealed class Cat(string Name) : Animal(Name){}

file struct Point(int x, int y)
{
    public int X => x;
    public int Y => y;
}

可以用于class 也可以用于 struct,相当于一个字段,可以直接在类型中直接引用类似的,对于构造函数依赖注入,我们也可以使用这个特性来进行简化,就不需要再手动声明字段了,示例如下:

file sealed class CrudHelper<T>(IIdGenerator idGenerator)
{
    public CrudHelper(): this(GuidIdGenerator.Instance)
    {
    }
    public string Create(T t)
    {
        // Biu...
        return idGenerator.NewId();
    }
}

What's inside

那么它实际是如何工作的呢,我们反编译看下前面的 CrudHelper, 反编译结果如下:

internal sealed class <PrimaryConstructorSample>FB47A7FDD6A2F32139FF8A728FBC1FE107CACFA93EA6284446EA366B2274F584F__CrudHelper<T>
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private IIdGenerator<idGenerator> P;

    public <PrimaryConstructorSample>FB47A7FDD6A2F32139FF8A728FBC1FE107CACFA93EA6284446EA366B2274F584F__CrudHelper(IIdGenerator idGenerator)
    {
  < idGenerator > P = idGenerator;
        base..ctor();
    }

    public <PrimaryConstructorSample>FB47A7FDD6A2F32139FF8A728FBC1FE107CACFA93EA6284446EA366B2274F584F__CrudHelper()
  : this((IIdGenerator)GuidIdGenerator.Instance)
 {
    }

    public string Create(T t)
    {
        return < idGenerator > P.NewId();
    }
}

从反编译结果可以看出来,实际就是生成了一个私有的字段,并且在构造方法里进行了赋值,而且字段上增加了 DebuggerBrowsable(DebuggerBrowsableState.Never) Attribute仔细看的话会发现字段不是 readonly 的,关于这一点,第一眼觉得,是不是可以声明称 readonly 的字段,目前来说字段是可以被修改的,不是 readonly 的,而且大概率以后也会保持这样,这样有着更大的灵活性,以后如果允许指定 access level(public/protected/..) 也不需要再变更这个字段,详细可以参考这个讨论:https://github.com/dotnet/csharplang/discussions/7377所以下面的示例也是可以编译通过的

file sealed class Dog(string name, int age) : Animal(name)
{
    public int Age => age;

    public void OneYearPassed()
    {
        age++;
        System.Console.WriteLine("One year passed, now age is: {0}", age);
    }
}


var dog = new Dog("Spike", 3);
Console.WriteLine(dog.Age);
dog.OneYearPassed();

输出结果如下:

3One year passed, now age is: 4

More

需要注意,非 record 类型的 primary constructor 和 record 类型的 primary constructor 并不相同,差别较大,对于 record 类型来说会生成属性而不是字段,这一点区别很大,而且目前来说字段是私有的,不能被外部类型直接引用,其次 record 类型会生成 deconstructor 和自定义的相等性比较等简而言之,非 record 类型的 primary constructor 更简单一些,只有一个字段更多