Каковы преимущества для пометки поля как «только для чтения» в C #?


295

В чем преимущества объявления переменной-члена только для чтения? Это просто защита от того, чтобы кто-то изменил его значение в течение жизненного цикла класса, или использование этого ключевого слова приводит к каким-либо улучшениям скорости или эффективности?


8
Хороший внешний ответ: dotnetperls.com/readonly
OneWorld

3
Интересный. По сути, это эквивалент C # этого вопроса Java. Stackoverflow.com/questions/137868/… Обсуждение здесь гораздо менее горячо, хотя ... хм ...
RAY

7
Может быть, стоит отметить, что readonlyполя структурных типов налагают снижение производительности по сравнению с изменяемыми полями, которые просто не видоизменяются, поскольку вызов любого члена поля типа readonlyзначения заставит компилятор сделать копию поля и вызвать член на этом.
суперкат

2
больше о снижении
CAD bloke

Ответы:


171

readonlyИспользуется ключевое слово , чтобы объявить переменную - член постоянной, но позволяет значение , которое будет вычисленного во время выполнения. Это отличается от константы, объявленной с constмодификатором, значение которого должно быть установлено во время компиляции. Используя readonlyвы можете установить значение поля либо в объявлении, либо в конструкторе объекта, членом которого является поле.

Также используйте его, если вы не хотите перекомпилировать внешние DLL, которые ссылаются на константу (так как она заменяется во время компиляции).


193

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

Однако «только для чтения» очень отличается от других типов семантики «только для чтения», потому что она применяется CLR во время выполнения. Ключевое слово readonly компилируется в .initonly, что проверяется CLR.

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

Вот хорошая запись об одном из преимуществ неизменности: Threading


6
Если вы читаете это, stackoverflow.com/questions/9860595/… член только для чтения может быть изменен и выглядит как противоречивое поведение .net
Akash Kava

Можете ли вы обновить ссылку на пост на Threading выше? Оно сломано.
Акшай Хот

69

Нет никаких очевидных преимуществ с точки зрения производительности readonly, по крайней мере, ни один из тех, о которых я когда-либо упоминал Это просто для того, чтобы делать то, что вы предлагаете, для предотвращения изменения после его инициализации.

Так что это выгодно тем, что помогает вам писать более надежный и читаемый код. Реальная выгода от подобных вещей приходит, когда вы работаете в команде или для технического обслуживания. Объявление чего-либо как readonlyродственное заключению контракта на использование этой переменной в коде. Думайте об этом как о добавлении документации таким же образом, как и другие ключевые слова, такие как internalили private, вы говорите: «эта переменная не должна изменяться после инициализации», и, более того, вы применяете ее.

Таким образом, если вы создаете класс и помечаете некоторые переменные-члены в соответствии readonlyс дизайном, то вы предотвращаете ошибку себя или другого члена команды позже, когда они расширяют или модифицируют ваш класс. На мой взгляд, это преимущество, которое стоит иметь (за счет небольшой дополнительной языковой сложности, как упоминалось в комментариях).


И Ото упрощает язык. Не отрицая вашего заявления о выгоде, однако.
dkretz

3
Я согласен, но я полагаю, что реальная выгода приходит, когда над кодом работает более одного человека. Это похоже на то, как если бы в коде было небольшое заявление о дизайне, контракт на его использование. Я, наверное, должен поставить это в ответ, хе-хе.
Сяофу

7
Этот ответ и обсуждение на самом деле лучший ответ на мой взгляд +1
Джефф Мартин

@Xiaofu: Вы сделали меня постоянной идеей только для чтения, хахаха, красивое объяснение, что никто в этом мире не может объяснить самый глупый ум понять
ученик

Т.е. вы намерены сохранить в своем коде, что это значение не должно изменяться в любое время.
Andez

51

Чтобы выразить это в очень практических условиях:

Если вы используете const в dll A и dll B ссылаются на const, то значение этого const будет скомпилировано в dll B. Если вы переустановите dll A с новым значением для этого const, dll B все равно будет использовать исходное значение.

Если вы используете только для чтения в dll A и dll B ссылки, которые только для чтения, то только для чтения всегда будет просматриваться во время выполнения. Это означает, что если вы повторно внедрите dll A с новым значением для этого только для чтения, dll B будет использовать это новое значение.


4
Это хороший практический пример, чтобы понять разницу. Спасибо.
Shyju

С другой стороны, constможет иметь выигрыш в производительности readonly. Вот немного более глубокое объяснение с кодом: dotnetperls.com/readonly
Dio

2
Я думаю, что самый большой практический термин отсутствует в этом ответе: способность хранить значения, вычисленные во время выполнения, в readonlyполя. Вы не можете хранить new object();в a, constи это имеет смысл, потому что вы не можете запекать не имеющие значения вещи, такие как ссылки на другие сборки, во время компиляции без изменения идентичности.
Бинки

14

Существует потенциальный случай, когда компилятор может оптимизировать производительность на основе наличия ключевого слова readonly.

Это применимо только в том случае, если поле только для чтения также помечено как статическое . В этом случае JIT-компилятор может предположить, что это статическое поле никогда не изменится. JIT-компилятор может принять это во внимание при компиляции методов класса.

Типичный пример: ваш класс может иметь статическое поле IsDebugLoggingEnabled , предназначенное только для чтения , которое инициализируется в конструкторе (например, на основе файла конфигурации). Как только фактические методы скомпилированы JIT, компилятор может пропустить целые части кода, когда ведение журнала отладки не включено.

Я не проверял, реализована ли эта оптимизация в текущей версии JIT-компилятора, так что это всего лишь предположение.


есть ли источник для этого?
Седат Капаноглу

4
Текущий JIT-компилятор действительно реализует это, и имеет начиная с CLR 3.5. github.com/dotnet/coreclr/issues/1079
mirhagk

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

9

Имейте в виду, что чтение только относится только к самому значению, поэтому, если вы используете ссылочный тип, только чтение только защищает ссылку от изменения. Состояние экземпляра не защищено только для чтения.


4

Не забывайте, что есть обходной путь, чтобы получить readonlyполя, установленные вне любых конструкторов, используя outparams.

Немного грязно, но:

private readonly int _someNumber;
private readonly string _someText;

public MyClass(int someNumber) : this(data, null)
{ }

public MyClass(int someNumber, string someText)
{
    Initialise(out _someNumber, someNumber, out _someText, someText);
}

private void Initialise(out int _someNumber, int someNumber, out string _someText, string someText)
{
    //some logic
}

Дальнейшее обсуждение здесь: http://www.adamjamesnaylor.com/2013/01/23/Setting-Readonly-Fields-From-Chained-Constructors.aspx


4
Поля все еще присваиваются в конструкторе .. нет никакого "обхода" этого. Неважно, если значения взяты из одного выражения, из разложенного сложного типа или назначены с помощью вызова по ссылочной семантике out..
user2864740

Это даже не попытка ответить на вопрос.
Шеридан

2

Удивительно, но чтение только на самом деле может привести к более медленному коду, как обнаружил Джон Скит при тестировании своей библиотеки Noda Time. В этом случае тест, выполненный за 20 секунд, занял всего 4 секунды после удаления только для чтения.

https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/


3
Обратите внимание, что если поле относится к типу, который есть readonly structв C # 7.2, преимущество в том, что поле не читается, исчезает.
Джон Скит

1

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


1

Добавление базового аспекта для ответа на этот вопрос:

Свойства могут быть выражены как только для чтения, опуская setоператора. Поэтому в большинстве случаев вам не нужно добавлять readonlyключевое слово в свойства:

public int Foo { get; }  // a readonly property

В отличие от этого: Полям необходимо readonlyключевое слово для достижения аналогичного эффекта:

public readonly int Foo; // a readonly field

Итак, одно преимущество маркировки поля, которое readonlyможет быть достигнуто, заключается в достижении аналогичного уровня защиты от записи как свойства без setоператора - без необходимости менять поле на свойство, если по какой-либо причине это желательно.


Есть ли разница в поведении между двумя?
petrosmm

0

Будьте осторожны с частными массивами только для чтения. Если они представляют клиента как объект (вы можете сделать это для COM-взаимодействия, как я), клиент может манипулировать значениями массива. Используйте метод Clone () при возврате массива в качестве объекта.


20
Нет; выставить ReadOnlyCollection<T>вместо массива.
SLaks

Это должен быть комментарий, а не ответ, поскольку он не дает ответа на вопрос ...
Питер

Как ни странно, мне сказали, что я делаю подобные вещи в качестве ответа, а не комментария, когда делаю это в другом посте на прошлой неделе.
Кайл Баран

Начиная с 2013 года, вы можете использовать ImmutableArray<T>, что позволяет избежать бокса для интерфейса ( IReadOnlyList<T>) или переноса в класс ( ReadOnlyCollection). Он имеет производительность, сравнимую с собственными массивами: blogs.msdn.microsoft.com/dotnet/2013/06/24/…
Чарльз Тейлор

0

В WPF может быть выигрыш в производительности, поскольку он устраняет необходимость в дорогих свойствах DependencyProperties. Это может быть особенно полезно с коллекциями


0

Другой интересной частью использования разметки только для чтения может быть защита поля от инициализации в синглтоне.

например в коде из csharpindepth :

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

readonly играет небольшую роль защиты поля Singleton от повторной инициализации. Другая деталь заключается в том, что для упомянутого сценария вы не можете использовать const, потому что const вызывает создание во время компиляции, а singleton создает во время выполнения.


0

readonlyможно инициализировать при объявлении или получить его значение только из конструктора. В отличие от constнего нужно инициализировать и объявить одновременно. readonly есть все const , плюс инициализация конструктора

код https://repl.it/HvRU/1

using System;

class MainClass {
    public static void Main (string[] args) {

        Console.WriteLine(new Test().c);
        Console.WriteLine(new Test("Constructor").c);
        Console.WriteLine(new Test().ChangeC()); //Error A readonly field 
        // `MainClass.Test.c' cannot be assigned to (except in a constructor or a 
        // variable initializer)
    }


    public class Test {
        public readonly string c = "Hello World";
        public Test() {

        }

        public Test(string val) {
          c = val;
        }

        public string ChangeC() {
            c = "Method";
            return c ;
        }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.