2016-10-03 5 views
10

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

Мне любопытно, почему до 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(); 
    } 
    } 
+3

Не могли бы вы показать нам код C# 5 и/или 6, который привел к этому IL? Ваше первое предложение смущает меня, потому что вы не можете автоматически инициализировать свойство до C# 6 - вам нужно было сделать это в конструкторе вручную. –

+0

Филе должно быть назначено перед ctor с инициализацией autoproperty (spec говорит так). И перед ctor - нет правильного поля инициализации свойства -> это делается в ctor -> использование свойств напрямую приведет к использованию унифицированных полей. – Mafii

+1

Вы должны показать весь фрагмент кода, в основном показать весь класс как как это было в C# 5 и как в C# 6, в противном случае мы можем только догадываться о причине, по которой компилятор ведет себя так, как это, по-видимому, для вас. –

ответ

6

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

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

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

С помощью этого кода, метод сеттер в дочернем классе не будет называться. Это намеренно. Инициализатор автоматического свойства предназначен для непосредственного задания поля подписи. Они выглядят и ведут себя как поле инициализаторы, поэтому мы можем даже использовать их на свойства без сеттеров, как это:

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

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

+0

кажется, что ваш ответ более уместен/полностью и объясняет точную разницу. i.e «Если компилятор должен был интерпретировать этот вызов метода как прямой вызов в поле поддержки, это не привело бы к вызову дочернего сеттера». Некоторые из других ответов также правильны, но могут быть немного сложными/не прямыми, или откусить тему. Благодаря! – Spock

0

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

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

    public string First { get; private set; } 
} 

, а затем в C# 6.0, единственное изменение, которое вы сделали, чтобы сделать Firstget -только autoproperty:

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

    public string First { get; } 
} 

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

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

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

+2

Я думаю, что речь идет о 'public string First {get; частный набор; } = «Адам»; ', где сеттер существует. – hvd

+0

@hvd Но как выглядит код C# 5.0? – svick

+2

'public string First {get; частный набор; } ', а затем в конструкторе' First = "Adam"; 'Другими словами, именно то, что вы думаете, похоже на код C# 5.0. – hvd

1

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

+1

Существует еще один момент: значения инициализированных свойств могут быть переопределены значениями, установленными в конструкторе. Это означает, что значения по умолчанию должны быть установлены перед конструктором (чтобы убедиться, что это произойдет). Но перед вызовом конструктора значения и методы еще не инициализируются (включая сеттеры). Вот почему они должны получить доступ к полям напрямую. – Mafii

+1

@Mafii IIRC, перед вызовом базового конструктора, все методы уже подлежат вызову, обычно это очень плохая идея, чтобы использовать это: спецификация CIL только говорит, что вызов методов экземпляра перед вызовом базового конструктора означает, что код не поддается проверке, и непроверяемый код все еще может быть правильным. – hvd

1

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

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

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

Именно поэтому инициализируются (к тому времени неинициализированные поля).

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


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

Example with property that is autoinitialized and changed in the ctor

Почему это не оптимизируется из?

См my question об этой теме:

Но она не должна оптимизировать, что вне дома?

Это, вероятно, могли бы, но только если этот класс не наследует от другого класса, который использует это значение в его конструкторе, он знает, что это авто-собственность и сеттер больше ничего не делать ,

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


Примечание стороны:

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

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

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

Это, кажется, самый прямой ответ на вопрос, и поэтому ИМО должен быть также принятым ответом. – Bauss

+1

@Mafi да компилятор сгенерированный код от JetBrains Dot Peek. – Spock

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

Обратите внимание, что если вы инициализировать свойство именно в конструкторе, и имеют геттер/сеттер свойство, в C# 6 он все равно будет выглядеть как первый кусок кода в моем ответе выше, в то время как если у вас есть поглотитель только поле будет выглядеть следующим образом:

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

Так что совершенно ясно, ваш C# 5 код выглядел первый кусок кода выше, и ваш код C# 6 выглядел как вторая часть кода.

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

Смежные вопросы