gpt4 book ai didi

c# - 在 C#9 中,仅初始化属性与只读属性有何不同?

转载 作者:行者123 更新时间:2023-12-04 11:18:54 27 4
gpt4 key购买 nike

我一直在阅读 C#9 中的 init-only 属性,但我认为我们已经有了只能在构造函数中设置的只读属性。在那之后,它是不可变的。
例如,在这里的类中,两个 NameDescription可以在构造函数中分配给,但仅限于那里,这正是 init-only 属性的描述方式。
示例类


class Thingy {

public Thingy(string name, string description){
Name = name;
Description = description;
}

public string Name { get; }
public string Description { get; }

public override string ToString()
=> $"{Name}: {Description}";
}
测试程序
using System;

class Program {

public static void Main (string[] args) {

var thingy = new Thingy("Test", "This is a test object");
Console.WriteLine(thingy);
// thingy.Name = “Illegal”; <— Won’t compile this line
}
}
这将输出以下内容:
Test: This is a test object
此外,如果我尝试修改 NameDescription构造函数运行后,它不会编译。
那么我错过了什么?

最佳答案

init访问器与 set 相同访问器在几乎所有领域的实现中,除了它以某种方式标记,使编译器禁止在一些特定上下文之外使用它。
相同我真的是指相同。创建的隐藏方法的名称是 set_PropertyName ,就像 set访问器,并且使用反射您甚至无法将它们区分开来,它们看起来是相同的(请参阅下面关于此的注释)。
不同之处在于,编译器使用此标志(更多内容见下文)将仅允许您在少数特定上下文中为 C# 中的属性设置值(下文也有更多内容)。

  • 从类型的构造函数或派生类型
  • 从对象初始值设定项,即。 new SomeType { Property = value }
  • 从带有新 with 的构造中关键字,即。 var copy = original with { Property = newValue }
  • 从内部 init另一个属性的访问器(因此一个 init 访问器可以写入其他 init 访问器属性)
  • 从属性说明符,所以你仍然可以写 [AttributeName(InitProperty = value)]

  • 在这些之外,基本上相当于正常的属性分配,编译器将阻止您写入带有编译器错误的属性,如下所示:

    CS8852 Init-only property or indexer 'Type.Property' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.


    因此,鉴于这种类型:
    public class Test
    {
    public int Value { get; init; }
    }
    您可以通过以下所有方式使用它:
    var test = new Test { Value = 42 };
    var copy = test with { Value = 17 };

    ...

    public class Derived : Test
    {
    public Derived() { Value = 42; }
    }

    public class ViaOtherInit : Test
    {
    public int OtherValue
    {
    get => Value;
    init => Value = value + 5;
    }
    }
    但你不能这样做:
    var test = new Test();
    test.Value = 42; // Gives compiler error
    因此,出于所有意图和目的,此类型是不可变的,但它现在允许您更轻松地构造该类型的实例,而不会陷入此不可变性问题。

    我在上面说过,反射并没有真正看到这一点,并注意我今天才了解实际机制,所以也许有一种方法可以找到一些可以真正区分差异的反射代码。重要的部分是编译器可以看到差异,这就是。
    鉴于类型声明为:
    public class Test
    {
    public int Value1 { get; set; }
    public int Value2 { get; init; }
    }
    那么为这两个属性生成的 IL 将如下所示:
    .property instance int32 Value1()
    {
    .get instance int32 UserQuery/Test::get_Value1()
    .set instance void UserQuery/Test::set_Value1(int32)
    }
    .property instance int32 Value2()
    {
    .get instance int32 UserQuery/Test::get_Value2()
    .set instance void modreq(System.Runtime.CompilerServices.IsExternalInit) UserQuery/Test::set_Value2(int32)
    }
    您可以看到 Value2属性 setter ( init 方法)已用 modreq(System.Runtime.CompilerServices.IsExternalInit) 标记/标记(不确定这些词是否正确,我确实说过我今天学到了这个) type 告诉编译器这个方法不是你叔叔的 set 访问器。
    这就是编译器如何知道如何将这个访问器方法与普通 set 区别对待。访问器(accessor)。
    给定 @canton7对这个问题的评论 modreq构造还意味着,如果您尝试在旧的 C# 编译器中使用使用新的 C# 9 编译器编译的库,则不会考虑此方法。这也意味着您将无法在对象初始值设定项中设置该属性,但这当然只能在 C# 9 和更新的编译器中使用。

    那么设置值的反射呢?好吧,事实证明反射将能够调用 init访问器就好了,这很好,因为这意味着反序列化,你可以说它是一种对象初始化,仍然会像你期望的那样工作。
    遵守以下内容 LINQPad程序:
    void Main()
    {
    var test = new Test();
    // test.Value = 42; // Gives compiler error
    typeof(Test).GetProperty("Value").SetValue(test, 42);
    test.Dump();
    }

    public class Test
    {
    public int Value { get; init; }
    }
    产生这个输出:
    output of reflection code
    这是一个 Json.net 示例:
    void Main()
    {
    var json = "{ \"Value\": 42 }";
    var test = JsonConvert.DeserializeObject<Test>(json);
    test.Dump();
    }
    这给出了与上面完全相同的输出。

    关于c# - 在 C#9 中,仅初始化属性与只读属性有何不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64749277/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com