Ответ 1
readonly
ли модификаторreadonly
скрытую копию поля?
Вызов метода или свойства в доступном только для чтения поле обычного типа структуры (вне конструктора или статического конструктора) сначала копирует поле, да. Это потому, что компилятор не знает, изменит ли свойство или метод доступа значение, которое вы вызываете.
Раздел 12.7.5.1 (Доступ членов, общий)
Это классифицирует доступ членов, в том числе:
- Если я идентифицирую статическое поле:
- Если поле доступно только для чтения и ссылка находится вне статического конструктора класса или структуры, в которой объявлено поле, то результатом является значение, а именно значение статического поля я в E.
- В противном случае результатом является переменная, а именно статическое поле я в E.
А также:
- Если T является типом структуры, и я идентифицирую поле экземпляра этого типа структуры:
- Если E является значением или если поле доступно только для чтения, а ссылка находится за пределами конструктора экземпляра структуры, в которой объявлено поле, то результатом является значение, а именно значение поля я в экземпляре структуры, заданное E.
- В противном случае результатом является переменная, а именно поле я в экземпляре структуры, заданном 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()
Это делает вызов непосредственно в поле, поэтому значение поля в конечном итоге изменяется внутри свойства.
Создание копии может быть неэффективным, когда структура велика и член не изменяет ее. Вот почему в С# 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
вместо структур.