Ответ 1
Основная причина пометить поле как readonly
так, чтобы вы знали, что обычный код не может заменить ссылку на список. Один из ключевых сценариев, когда это имеет значение, - это если у вас есть другой код в типе, который выполняет синхронизацию с списком с помощью lock(theListField)
. Очевидно, если кто-то поменяет экземпляр списка: все сломается. Обратите внимание, что в большинстве типов, у которых есть список/коллекция, не ожидается изменения экземпляра, поэтому этот readonly
утверждает это ожидание. Общий шаблон:
private List<Foo> _items = new List<Foo>();
public List<Foo> Items => _items;
или
public List<Foo> Items {get;} = new List<Foo>();
В первом примере это должно быть прекрасно, чтобы отметить это поле как readonly
:
private readonly List<Foo> _items = new List<Foo>();
Маркировка поля как readonly
не влияет на распределения и т.д. Он также не делает список доступным только для чтения: просто поле. Вы все еще можете Add()
/Remove()
/Clear()
и т.д. Единственное, что вы не можете сделать, это изменить экземпляр списка как совершенно другой экземпляр списка; вы, конечно, можете полностью изменить содержимое. И только чтение - ложь: отражение и небезопасный код могут изменять значение поля readonly
.
Существует один сценарий, в котором readonly
может иметь отрицательное воздействие и относится к большим полям struct
и вызовам на них. Если поле readonly
, компилятор копирует структуру в стек перед вызовом метода, а не выполняет метод на месте в поле; ldfld
+ stloc
+ ldloca
(если поле readonly
) vs ldflda
(если оно не отмечено readonly
); это связано с тем, что компилятор не может доверять методу не изменять значение. Он даже не может проверить, все ли в структуре readonly
, потому что этого недостаточно: метод struct
может переписать this
:
struct EvilStruct
{
readonly int _id;
public EvilStruct(int id) { _id = id; }
public void EvilMethod() { this = new EvilStruct(_id + 1); }
}
Поскольку компилятор пытается обеспечить природу поля readonly
, если у вас есть:
readonly EvilStruct _foo;
//...
_foo.EvilMethod();
он хочет убедиться, что EvilMethod()
не может перезаписать _foo
новым значением. Отсюда гимнастика и копия в стеке. Обычно это оказывает незначительное влияние, но если структура атипично велика, это может вызвать проблемы с производительностью. Та же проблема обеспечения того, что значение не изменяется, также относится к новому модификатору аргумента in
в С# 7.2:
void(in EvilStruct value) {...}
когда вызывающий абонент хочет гарантировать, что он не изменит значение (это фактически ref EvilStruct
, поэтому изменения будут распространяться).
Эта проблема разрешена в С# 7.2 добавлением синтаксиса readonly struct
- это говорит компилятору, что безопасно вызывать метод in-situ без необходимости делать дополнительную копию стека:
readonly struct EvilStruct
{
readonly int _id;
public EvilStruct(int id) { _id = id; }
// the following method no longer compiles:
// CS1604 Cannot assign to 'this' because it is read-only
public void EvilMethod() { this = new EvilStruct(_id + 1); }
}
Весь этот сценарий не относится к List<T>
, потому что это ссылочный тип, а не тип значения.