С# 6 Свойство автоматической инициализации и использование полей поддержки

До С# 6 инициализация свойств не использовала поля поддержки для инициализации значений по умолчанию. В С# 6 он использует поля поддержки для инициализации с помощью нового Свойства автоматической инициализации.

Мне любопытно, почему до С# 6 IL использует определение свойства для инициализации. Есть ли конкретная причина для этого? или он не применяется должным образом до С# 6?

До С# 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";
    }
  }

С# 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();
    }
  } 

Ответ 1

Мне любопытно, почему до С# 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 в дочернем классе. Помните, потому что вы вызываете метод? Если компилятор должен был интерпретировать этот вызов метода как прямой вызов в поле поддержки, это не вызовет вызов детского сеттера. Акт составления кода изменит поведение вашей программы. Нехорошо!

Авто-свойства разные. Позволяет изменить первый пример, используя инициализатор автоисточника:

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 в дочернем классе не будет вызываться. Это намеренно. Инициализатор автоматического свойства предназначен для непосредственного задания поля подписи. Они выглядят и ведут себя как инициализаторы полей, поэтому мы можем даже использовать их по свойствам без сеттеров, например:

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

Здесь нет метода setter! Для этого нам нужно было бы напрямую получить доступ к области поддержки. Авто-свойства позволяют программистам создавать неизменные значения, сохраняя при этом хороший синтаксис.

Ответ 2

Единственный раз, когда это имеет значение, - если у средства настройки свойств больше эффектов, чем просто установка значения. Для автоматически реализованных свойств единственное время, которое может произойти, это если они virtual и переопределены. В этом случае вызов метода производного класса до запуска конструктора базового класса - очень плохая идея. С# проходит множество неприятностей, чтобы убедиться, что вы случайно не попали в ссылки на еще не полностью инициализированные объекты. Поэтому он должен установить поле для предотвращения этого.

Ответ 3

Имейте в виду, что значения, установленные по умолчанию для свойств, не задаются в конструкторе (ваш код показывает, что: assigments, then constructor).

Теперь спецификация С# говорит, что значения autoinitialization задаются перед конструктором. Это имеет смысл: когда эти значения снова устанавливаются в конструкторе, они переопределяются.

Теперь - перед вызовом конструктора - не запущены методы getter и setter. Как их использовать?

Вот почему инициализируются (к тому времени неинициализированные поля поддержки).

Как упоминалось выше, также будет проблема с виртуальными вызовами, но они даже не инициализированы. Основная причина.


Он по-прежнему ведет себя так же, как и раньше, если вы назначаете значения в конструкторе:

Пример с атрибутом, который автоинициализирован и изменен в ctor

Почему это не оптимизировано?

Смотрите мой вопрос об этой теме:

Но не следует ли это оптимизировать?

Возможно, это возможно, но только если этот класс не наследуется от другого класс, который использует это значение в своем конструкторе, он знает, что он auto-property и setter ничего не делают.

Это было бы много (опасных) предположений. Компилятор должен проверьте многое, прежде чем делать такую ​​оптимизацию.


Боковое примечание:

Я предполагаю, что вы используете какой-то инструмент для просмотра сгенерированного компилятором кода С# - это не совсем точно. Там нет точного выражения для кода IL, который генерируется для конструктора - ctor не является методом в IL, его чем-то другим. Ради понимания мы можем предположить, что это то же самое.

http://tryroslyn.azurewebsites.net/ как пример имеет этот комментарий:

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

Ответ 4

Один из способов получить код, как показано, это то, что у вас есть код С# 5:

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

    public string A { get; set; }
}

Это приведет к созданию кода (IL) следующим образом:

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

Ваш код С# 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, в С# 6 он по-прежнему будет выглядеть как первый фрагмент кода в моем ответе выше, тогда как если у вас есть только поле только для геттера он будет выглядеть следующим образом:

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

Итак, совершенно очевидно, что ваш код на С# 5 выглядел как первый фрагмент кода выше, а ваш код С# 6 выглядел как вторая часть кода.

Итак, чтобы ответить на ваш вопрос: почему С# 5 и С# 6 ведут себя по-разному с точки зрения того, как он компилирует автоматическую инициализацию свойств? Причина в том, что вы не можете выполнять автоматическую инициализацию свойств в С# 5 или ранее, а другой код компилируется по-разному.

Ответ 5

Я предполагаю, что ваш код С# 5.0 выглядит следующим образом:

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

    public string First { get; private set; }
}

И затем в С# 6.0, единственное изменение, которое вы сделали, - сделать First a get - только автопроизводство:

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

    public string First { get; }
}

В случае с С# 5.0 First является свойством с установщиком, и вы используете его в конструкторе, поэтому сгенерированный IL отражает это.

В версии С# 6.0 First не имеет установщика, поэтому конструктор должен получить доступ к базовому полю напрямую.

Оба случая имеют для меня смысл.