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

时间:2016-10-03 21:27:37

标签: c# .net c#-6.0 intermediate-language

在C#6之前,属性的初始化没有使用支持字段来初始化默认值。 在C#6中,它使用支持字段来初始化新的Auto initialization properties

我很好奇为什么在C#6 IL之前使用属性定义进行初始化。这有什么特别的原因吗?或者在C#6之前没有正确实施?

在C#6.0之前

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

    public string Last { get; set; }

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

编译器生成的代码(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

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

编译器生成的代码(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();
    }
  } 

5 个答案:

答案 0 :(得分:9)

  

我很好奇为什么在C#6 IL之前使用属性定义进行初始化。这是否有特定的原因?

因为通过自动属性初始化设置值并在构造函数中设置值是两回事。他们有不同的行为。

回想一下,属性是包含字段的访问器方法。所以这一行:

this.First = "Adam";

相当于:

this.set_First("Adam");

您甚至可以在Visual Studio中看到这一点!尝试在你的班级中编写一个带有签名public string set_First(string value)的方法,并观察编译器抱怨你踩到它的脚趾。

就像方法一样,这些可以在子类中重写。看看这段代码:

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;
        }
    }
}

在此示例中,行this.First = "Adam"将调用子类中的setter。因为你正在调用一种方法,还记得吗?如果编译器将此方法调用解释为对支持字段的直接调用,则它不会最终调用子setter。 编译代码的行为会改变程序的行为。不好!

自动属性不同。让我们使用自动属性初始值设定项来更改第一个示例:

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;
        }
    }
}

使用此代码,不会调用子类中的setter方法。这是故意的。自动属性初始值设定项用于直接设置支持字段。它们的外观和行为类似于字段初始化器,这就是为什么我们甚至可以在没有setter的属性上使用它们,如下所示:

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

这里没有setter方法!我们必须直接访问支持字段才能执行此操作。自动属性允许程序员创建不可变值,同时仍然可以从良好的语法中受益。

答案 1 :(得分:1)

它唯一能产生影响的是,如果属性设置器的效果比简单设置值更多。对于自动实现的属性,唯一可能发生的时间是它们是virtual并被覆盖。在这种情况下,在基类构造函数运行之前调用派生类方法是一个非常糟糕的主意。 C#经历了很多麻烦,以确保您不会意外地结束对尚未完全初始化的对象的引用。所以它必须直接设置字段以防止这种情况。

答案 2 :(得分:1)

请记住,未在构造函数中设置属性的默认值(您的代码显示:assigments,然后是构造函数)。

现在,C#规范说自动初始化值是在构造函数之前设置的。这是有道理的:当在构造函数中再次设置这些值时,它们将被覆盖。

现在 - 在调用构造函数之前 - 没有初始化的getter和setter方法。它们应该如何使用?

这就是为什么(通过当时未初始化的后备字段)直接初始化。

正如hvd所提到的,虚拟调用也会出现问题,但是它们甚至没有被初始化是主要原因。

如果在构造函数中指定值,它的行为仍然与以前相同:

Example with property that is autoinitialized and changed in the ctor

为什么不对此进行优化?

有关此主题的信息,请参阅my question

但是不应该优化它吗?

  

它可能,但只有当该类不从另一个继承时   在其构造函数中使用该值的类,它知道它是一个   自动属性和setter没有做任何其他事情。

     

这将是很多(危险的)假设。编译器需要   在进行这样的优化之前检查很多东西。

旁注:

我假设您使用某种工具来查看编译器生成的c#代码 - 它并不完全准确。对于构造函数生成的IL代码没有准确的表达式 - ctor不是IL中的方法,它的东西是不同的。为了便于理解,我们可以假设它是相同的。

http://tryroslyn.azurewebsites.net/例如有这样的评论:

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

答案 3 :(得分:1)

您可以获得所示代码的一种方法是使用C#5代码:

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

    public string A { get; set; }
}

这将生成(IL)代码,如下所示:

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

您的C#6代码如下所示:

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

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

生成(IL)代码如下:

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

请注意,如果您在构造函数中专门初始化属性,并且具有getter / setter属性,那么在C#6中它仍然看起来像我上面的答案中的第一段代码,而如果你有一个getter-only字段它看起来像这样:

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

所以很明显,你的C#5代码看起来像上面的第一段代码,而你的C#6代码看起来就像是第二段代码。

所以回答你的问题:为什么C#5和C#6在编译自动属性初始化方面表现不同?原因是您无法在C#5或之前进行自动属性初始化,并且不同的代码编译方式不同。

答案 4 :(得分:0)

我假设您的C#5.0代码如下所示:

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

    public string First { get; private set; }
}

然后在C#6.0中,您所做的唯一更改是将First设为get - 仅限autoproperty:

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

    public string First { get; }
}

在C#5.0案例中,First是一个带有setter的属性,您可以在构造函数中使用它,因此生成的IL反映了这一点。

在C#6.0版本中,First没有setter,因此构造函数必须直接访问支持字段。

这两种情况对我来说都很有意义。