С# 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
не имеет установщика, поэтому конструктор должен получить доступ к базовому полю напрямую.
Оба случая имеют для меня смысл.