Создает ли модификатор readonly
скрытую копию поля?
Вызов метода или свойства в доступном только для чтения поле обычного типа структуры (вне конструктора или статического конструктора) сначала копирует поле, да. Это потому, что компилятор не знает, изменит ли свойство или метод доступа значение, к которому вы его вызываете.
Из спецификации C # 5 ECMA :
Раздел 12.7.5.1 (Доступ участника, общий)
Классифицирует доступ членов, включая:
- Если я идентифицирую статическое поле:
- Если поле доступно только для чтения и ссылка находится вне статического конструктора класса или структуры, в которой оно объявлено, то результатом является значение, а именно значение статического поля I в E.
- В противном случае результатом является переменная, а именно статическое поле I в E.
И
- Если T является структурным типом, и я идентифицирую поле экземпляра этого структурного типа:
- Если E является значением или если поле доступно только для чтения и ссылка находится за пределами конструктора экземпляра структуры, в которой объявлено поле, то результатом является значение, а именно значение поля I в структуре экземпляр, данный Е.
- В противном случае результатом является переменная, а именно поле I в экземпляре структуры, заданном E.
Я не уверен, почему часть поля экземпляра специально относится к типам структуры, а часть статического поля - нет. Важной частью является то, классифицируется ли выражение как переменная или значение. Это важно при вызове члена функции ...
Раздел 12.6.6.1 (Общий вызов элемента функции)
Обработка во время выполнения вызова элемента функции состоит из следующих шагов, где M - это элемент функции, а если M - это элемент экземпляра, E - выражение экземпляра:
[...]
- В противном случае, если тип E является типом значения V, а M объявляется или переопределяется в V:
- [...]
- Если E не классифицируется как переменная, то создается временная локальная переменная типа E, и этой переменной присваивается значение E. Затем E переклассифицируется как ссылка на эту временную локальную переменную. Временная переменная доступна как это в пределах M, но никаким другим способом. Таким образом, только когда E является истинной переменной, вызывающий может наблюдать изменения, которые M вносит в это.
Вот отдельный пример:
using System;
using System.Globalization;
struct Counter
{
private int count;
public int IncrementedCount => ++count;
}
class Test
{
static readonly Counter readOnlyCounter;
static Counter readWriteCounter;
static void Main()
{
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 2
Console.WriteLine(readWriteCounter.IncrementedCount); // 3
}
}
Вот IL для вызова readOnlyCounter.IncrementedCount
:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
Это копирует значение поля в стек, затем вызывает свойство ... чтобы значение поля не заканчивалось изменением; увеличивается count
внутри копии.
Сравните это с IL для поля чтения-записи:
ldsflda valuetype Counter Test::readWriteCounter
call instance int32 Counter::get_IncrementedCount()
Это делает вызов непосредственно в поле, поэтому значение поля в конечном итоге изменяется внутри свойства.
Создание копии может быть неэффективным, если структура большая, а член не не изменяет ее. Вот почему в C # 7.2 и выше модификатор readonly
может применяться к структуре. Вот еще один пример:
using System;
using System.Globalization;
readonly struct ReadOnlyStruct
{
public void NoOp() {}
}
class Test
{
static readonly ReadOnlyStruct field1;
static ReadOnlyStruct field2;
static void Main()
{
field1.NoOp();
field2.NoOp();
}
}
С модификатором readonly
в самой структуре вызов field1.NoOp()
не создает копию. Если вы удалите модификатор readonly
и перекомпилируете, вы увидите, что он создает копию так же, как в readOnlyCounter.IncrementedCount
.
У меня есть запись в блоге за 2014 год , которую я написал, обнаружив, что поля readonly
вызывают проблемы с производительностью в Noda Time. К счастью, теперь это исправлено с помощью модификатора readonly
на структурах.