简体   繁体   English

C#6自动初始化属性和支持字段的使用

[英]C# 6 Auto Initialization Property and the use of backing fields

Prior to C# 6, the initialization of properties did not use backing fields to initialize default values. 在C#6之前,属性的初始化不使用支持字段来初始化默认值。 In C#6, it uses the backing fields to initialize with new Auto initialization properties . 在C#6中,它使用支持字段来初始化新的自动初始化属性

I'm curious why prior to C#6 IL uses the property definition to initialize. 我很好奇为什么在C#6 IL之前使用属性定义进行初始化。 Is there a specific reason for this? 这有什么特别的原因吗? or is it not implemented properly before C#6? 或者在C#6之前没有正确实施?

Before C# 6.0 在C#6.0之前

public class PropertyInitialization
{
    public string First { get; set; }

    public string Last { get; set; }

    public PropertyInitialization()
    {
      this.First = "Adam";
      this.Last = "Smith";
    }
}

Compiler Generated Code (IL representation) 编译器生成代码(IL表示)

public class PropertyInitialisation
  {
    [CompilerGenerated]
    private string \u003CFirst\u003Ek__BackingField;
    [CompilerGenerated]
    private string \u003CLast\u003Ek__BackingField;

    public string First
    {
      get
      {
        return this.\u003CFirst\u003Ek__BackingField;
      }
      set
      {
        this.\u003CFirst\u003Ek__BackingField = value;
      }
    }

    public string Last
    {
      get
      {
        return this.\u003CLast\u003Ek__BackingField;
      }
      set
      {
        this.\u003CLast\u003Ek__BackingField = value;
      }
    }

    public PropertyInitialisation()
    {
      base.\u002Ector();
      this.First = "Adam";
      this.Last = "Smith";
    }
  }

C#6 C#6

public class AutoPropertyInitialization
{
    public string First { get; set; } = "Adam";
    public string Last { get; set; } = "Smith";
}

Compiler Generated Code (IL representation) 编译器生成代码(IL表示)

public class AutoPropertyInitialization
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string \u003CFirst\u003Ek__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string \u003CLast\u003Ek__BackingField;

    public string First
    {
      get
      {
        return this.\u003CFirst\u003Ek__BackingField;
      }
      set
      {
        this.\u003CFirst\u003Ek__BackingField = value;
      }
    }

    public string Last
    {
      get
      {
        return this.\u003CLast\u003Ek__BackingField;
      }
      set
      {
        this.\u003CLast\u003Ek__BackingField = value;
      }
    }

    public AutoPropertyInitialization()
    {
      this.\u003CFirst\u003Ek__BackingField = "Adam";
      this.\u003CLast\u003Ek__BackingField = "Smith";
      base.\u002Ector();
    }
  } 

I'm curious why prior to C#6 IL uses the property definition to initialize. 我很好奇为什么在C#6 IL之前使用属性定义进行初始化。 Is there a specific reason for this? 这有什么特别的原因吗?

Because setting a value through auto-property initialization and setting the value in a constructor are two different things. 因为通过自动属性初始化设置值并在构造函数中设置值是两回事。 They have different behaviours. 他们有不同的行为。

Recall that properties are accessor methods which wrap around fields. 回想一下,属性是包含字段的访问器方法。 So this line: 所以这一行:

this.First = "Adam";

is equivalent to: 相当于:

this.set_First("Adam");

You can even see this in Visual Studio! 你甚至可以在Visual Studio中看到这个! Try writing a method with the signature public string set_First(string value) in your class and watch as the compiler complains about you stepping on it's toes. 尝试在类中使用签名public string set_First(string value)编写一个方法,并观察编译器抱怨你踩到它的脚趾。

And just like methods, these can be overridden in child classes. 就像方法一样,这些可以在子类中重写。 Check out this code: 看看这段代码:

public class PropertyInitialization
{
    public virtual string First { get; set; }

    public PropertyInitialization()
    {
        this.First = "Adam";
    }
}

public class ZopertyInitalization : PropertyInitialization
{
    public override string First
    {
        get { return base.First; }
        set
        {
            Console.WriteLine($"Child property hit with the value: '{0}'");
            base.First = value;
        }
    }
}

In this example, the line this.First = "Adam" will call the setter in the child class. 在此示例中, this.First = "Adam"行将调用子类中的setter。 Because you're calling a method, remember? 因为你正在调用一种方法,还记得吗? If the compiler were to interpret this method call as a direct call to the backing field, it wouldn't end up calling the child setter. 如果编译器将此方法调用解释为对支持字段的直接调用,则它不会最终调用子setter。 The act of compiling your code would change the behaviour of your program. 编译代码的行为会改变程序的行为。 Not good! 不好!

Auto-properties are different. 自动属性是不同的。 Lets change the first example by using an auto-property initializer: 让我们使用auto-property初始值设定项来改变第一个例子:

public class PropertyInitialization
{
    public virtual string First { get; set; } = "Adam";
}

public class ZopertyInitalization : PropertyInitialization
{
    public override string First
    {
        get { return base.First; }
        set
        {
            Console.WriteLine($"Child property hit with the value: '{0}'");
            base.First = value;
        }
    }
}

With this code, the setter method in the child class will not be called. 使用此代码,将不会调用子类中的setter方法。 This is intentional. 这是故意的。 An auto-property initializer is designed to set the backing field directly. 自动属性初始值设定项用于直接设置支持字段。 They look and behave like field initializers, which is why we can even use them on properties without setters, like this: 它们的外观和行为类似于字段初始化程序,这就是为什么我们甚至可以在没有setter的属性上使用它们,如下所示:

public string First { get; } = "Adam";

There's no setter method here! 这里没有setter方法! We would have to directly access the backing field to do this. 我们必须直接访问支持字段才能执行此操作。 Auto-properties allow programmers to create immutable values while still being able to benefit from nice syntax. 自动属性允许程序员创建不可变值,同时仍然可以从良好的语法中受益。

The only time it makes a difference is if the property setter has more effects than simply setting the value. 唯一不同的是,如果属性设置器具有比简单设置值更多的效果。 For auto-implemented properties, the only time that can happen is if they are virtual and overridden. 对于自动实现的属性,唯一可能发生的是它们是virtual还是被覆盖的。 In that case, calling the derived class method before the base class constructor has run is a very bad idea. 在这种情况下,在基类构造函数运行之前调用派生类方法是一个非常糟糕的主意。 C# goes through a lot of trouble to make sure you do not accidentally end up with references to not yet fully initialised objects. C#经历了很多麻烦,以确保您不会意外地结束对尚未完全初始化的对象的引用。 So it has to set the field directly to prevent that. 因此必须直接设置字段以防止这种情况发生。

Keep in mind that values set as default for properties are not being set in the constructor (your code shows that: assigments, then constructor). 请记住,在构造函数中没有设置属性的默认值(您的代码显示:assigments,然后是构造函数)。

Now, the C# spec says that autoinitialization values are set before the constructor. 现在,C#规范说自动初始化值是在构造函数之前设置的。 This makes sense: When these values are set again in the constructor, they are overridden. 这是有道理的:当在构造函数中再次设置这些值时,它们将被覆盖。

Now - before the constructor is called - there are no getter and setter methods initialized. 现在 - 在调用构造函数之前 - 没有初始化的getter和setter方法。 How should they be used? 它们应该如何使用?

Thats why the (by then uninitialized backing-fields) are being initialized directly. 这就是为什么(通过当时未初始化的后备字段)直接初始化。

As hvd mentioned, there would also be a problem with virtual calls, but that they aren't even initialized is the main reason. 正如hvd所提到的,虚拟调用也会出现问题,但它们甚至没有被初始化是主要原因。


It still behaves the same way as before if you assign values in the constructor: 如果在构造函数中指定值,它的行为仍然与以前相同:

Example with property that is autoinitialized and changed in the ctor 具有在ctor中自动初始化和更改的属性的示例

Why isn't this being optimized out? 为什么不对此进行优化?

See my question about this topic: 请参阅关于此主题的问题:

But shouldn't it optimize that out? 但它不应该优化出来吗?

It probably could, but only if that class doesn't inherit from another class that uses that value in its constructor, it knows that it's an auto-property and the setter doesn't do anything else. 它可能,但只有当该类不从其构造函数中使用该值的另一​​个类继承时,它才知道它是一个自动属性,而setter不会做任何其他事情。

That would be a lot of (dangerous) assumptions. 这将是很多(危险的)假设。 The compiler needs to check a lot of things before making an optimization like that. 在进行这样的优化之前,编译器需要检查很多东西。


Side note: 边注:

I assume you use some tool for seeing the compiler generated c# code - it's not entirely accurate. 我假设您使用一些工具来查看编译器生成的c#代码 - 它并不完全准确。 There's no accurate expression for the IL code that is being generated for a constructor - the ctor is not a method in IL, its something different. 对于构造函数生成的IL代码没有准确的表达式 - ctor不是IL中的方法,它的东西是不同的。 For the sake of understanding we can assume it is the same tho. 为了便于理解,我们可以假设它是相同的。

http://tryroslyn.azurewebsites.net/ as example has this comment: http://tryroslyn.azurewebsites.net/作为示例有这样的评论:

// This is not valid C#, but it represents the IL correctly.

One way you can get the code as shown is that you have your C# 5 code like this: 您可以获得所示代码的一种方法是使用C#5代码:

public class Test : Base
{
    public Test()
    {
        A = "test";
    }

    public string A { get; set; }
}

This will produce (IL) code like this: 这将生成(IL)代码,如下所示:

public Test..ctor()
{
    Base..ctor();
    A = "test";
}

Your C# 6 code will look like this: 您的C#6代码如下所示:

public class Test : Base
{
    public Test()
    {
    }

    public string A { get; set; } = "test";
}

Which produces (IL) code like this: 哪个产生(IL)代码如下:

public Test..ctor()
{
    <A>k__BackingField = "test";
    Base..ctor();
}

Note that if you initialize your property specifically in the constructor, and have a getter/setter property, in C# 6 it will still look like the first piece of code in my answer above, whereas if you have a getter-only field it will look like this: 请注意,如果你在构造函数中专门初始化你的属性,并且具有getter / setter属性,那么在C#6中它仍然看起来像我上面的答案中的第一段代码,而如果你有一个getter-only字段它会看起来像这样:

public Test..ctor()
{
    Base..ctor();
    <A>k__BackingField = "test";
}

So it is quite clear, your C# 5 code looked like the first piece of code above, and your C# 6 code looked like the second piece of code. 所以很明显,你的C#5代码看起来像上面的第一段代码,你的C#6代码看起来像第二段代码。

So to answer your question: Why does C# 5 and C# 6 behave differently in terms of how it compiles automatic property initialization? 那么回答你的问题:为什么C#5和C#6在编译自动属性初始化方面表现不同? The reason is because you cannot do automatic property initialization in C# 5 or prior, and different code compiles differently. 原因是您无法在C#5或之前进行自动属性初始化,并且不同的代码编译方式不同。

I'm assuming your C# 5.0 code looked like this: 我假设您的C#5.0代码如下所示:

class C
{
    public C()
    {
        First = "Adam";
    }

    public string First { get; private set; }
}

And then in C# 6.0, the only change you made is to make First a get -only autoproperty: 然后在C#6.0中,你所做的唯一改变是使First成为一个get -only autoproperty:

class C
{
    public C()
    {
        First = "Adam";
    }

    public string First { get; }
}

In the C# 5.0 case, First is a property with a setter and your use it in the constructor, so the generated IL reflects that. 在C#5.0的情况下, First是一个带有setter的属性,你可以在构造函数中使用它,因此生成的IL反映了这一点。

In the C# 6.0 version, First does not have a setter, so the constructor has to access the backing field directly. 在C#6.0版本中, First没有setter,因此构造函数必须直接访问支持字段。

Both cases make perfect sense to me. 这两种情况对我来说都很有意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C#匿名支持具有非自动属性的字段 - C# anonymous backing fields with non-auto properties 在给定支持字段的情况下获取C#auto属性的PropertyInfo - Get PropertyInfo of a C# auto property given the backing field 我可以为 C# 自动实现的属性(也称为自动支持字段)定义自定义 getter 吗? - Can I define a custom getter for a C# auto-implemented property (a.k.a. auto backing field)? 为什么在C#6.0自动属性初始化中没有`this`? - Why is `this` not available in C# 6.0 Auto-Property Initialization? C#6.0反射:提取只读自动属性的支持字段的名称 - C# 6.0 Reflection: Extract the name of backing fields of read-only auto properties 如何在Visual Studio调试器中查看C#自动属性的支持字段? - How to view backing fields for C# auto properties in Visual Studio debugger? c#BsonSerializer:通过支持字段反序列化 - c# BsonSerializer: deserialization through backing fields 格式化C#中属性的Resharper支持字段 - Formatting Resharper backing fields for properties in C# 格式化C#中属性的Resharper支持字段 - Formatting Resharper backing fields for properties in C# C#字段初始化的顺序 - C# The order of fields initialization
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM