Яндекс.Метрика

    Песочница

    Const vs Readonly (C#)

    Привет!

    Как вы все наверно знаете, практика хорошего программирования подразумевает, что разработчик будет использовать константы в своем коде вместо прямого использования чисел и строк. Иными словами – никаких магических чисел. Согласитесь, куда понятнее для других будет строчка a = MaxItemCount, чем a = 250.

    О том, как правильно объявлять константы, разговоры ходят довольно давно. Еще в языке C был спор, что лучше – const или define. На этот вопрос я сегодня отвечать не буду, но зато расскажу про другой спор из языка C# – const или readonly. Это не спор вовсе, а скорее совет по выбору лучшей техники.
    Что такое readonly?

    Думаю не стоит объяснять вам, что такое const. Наверняка все вы так или иначе знаете про это ключевое слово, так как оно присутствует во многих современных языках программирования. Const, написанный перед объявлением переменной, делает из нее константу – число, строку или иной простой тип, который не должен и не может изменяться. А вот readonly – это фишка именно C#, поэтому про нее поподробнее.
    Если обратиться к документации, то readonly – это модификатор, который может быть применим к полям класса. Если поле объявлено как readonly, то его значение может быть задано либо непосредственно в момент объявления, либо в конструкторе класса.

    private readonly int MaxNumberOfItems = 250;


    или

    public class SampleClass
    {
    	private readonly int MaxNumberOfItems;
     
    	public SampleClass()
    	{
    		MaxNumberOfItems = 250;
    	}
    }


    На первый взгляд разницы никакой, однако это не так. У readonly есть главное преимущество – его можно инициализировать в runtime, то есть в момент работы приложения. Давайте разберемся, что это нам дает.

    Fight!



    Наш бой между двумя способами объявления констант пройдет в три раунда. В первом раунде я расскажу об упомянутой выше инициализации в момент выполнения кода.

    Давайте рассмотрим пример. Пусть мы разрабатываем некое приложение, которое производит некие валютные операции. Это приложение обязательно будет работать с курсами валют, которые должны с одной стороны меняться каждый день, а с другой – оставаться неизменными в течении работы приложения. Это можно реализовать, например, свойством с приватным сеттером, но в таком случае остается риск, что какой-то нерадивый программист, используя ваш класс, захочет обхитрить систему и с помощью reflection попытается модифицировать данные значения.

    Лучше всего для задания курсов валют подошли бы константы, но вот беда – константы должны быть прописаны на этапе компиляции, а мы не можем знать их значения заранее. И вот тут то нам и поможет readonly. Константа, объявленная как readonly, может быть проинициализирована в конструкторе, а это значит, что ей может быть присвоено значение одного из его параметров. Вот так мы добились поставленной цели легко и просто.

    Раунд 2



    Аргументы в данном раунде следуют из предыдущего. Для тех полей, которые объявлены как const, мы можем задать значение только в момент объявления, т.е. в той же самой строке (см. первую вставку кода выше). Однако как быть, если в качестве постоянной величины нам надо задать значение типа DateTime, например наш день рождения? Ну ладно, DateTime может быть почти полностью задан конструктором. А если у нас есть свой класс, в котором куча полей и который невозможно задать за одно выражение, хотя с точки зрения логики данный класс – нечто постоянное (ну например данные о человеке: ФИО, адрес)?

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

    Раунд 3.



    Ну и немного неочевидный, но тем не менее весомый аргумент. Константы, которые объявлены как const, получают свое значение в момент компиляции и компилятор встраивает их в программу не как переменные, а уже как их подставленные значения. Причем не только внутри сборки, где эта константа объявлена, но и по всему приложению, которое использует данную сборку как стороннюю. Из этого факта рождается следующий порок.

    Допустим мы разрабатываем некую публичную библиотеку, которой пользуются многие, но не даем исходного кода, а распространяем ее в виде бинарного файла (dll). И вот в нашей сборке мы объявили константой некое значение и повсеместно советуем его использовать. Прошло время, нашей библиотекой пользуются многие организации, отзывы о нас становятся все лучше и лучше и мы решает развивать проект. И тут оказывается, что старое значение константы несколько неверно и нам надо его подкорректировать, изменив с 10 на 100. Не беда – исправляем значение, пересобираем проект и просим наших пользователей обновить библиотеку в своих проектах, просто заменив старую на новую.

    Проходит время и в наш адрес стали сыпаться тонны негативных отзывов, что новая версия работает плохо, выдает неверные результаты или просто зацикливается. Многие откатываются к предыдущей версии и требуют объяснений. Но ведь у нас же все работает!!! И тесты выдают корректные результаты!

    В чем же дело? Я сказал выше, что поля, объявленые как const, компилируются и вставляются как свои значения по всему приложению, даже вне сборки, где они объявлены. В нашем примере мы стали жертвой именно такой ситуации – мы поменяли значение у поля const, но наши клиенты не пересобрали свои проекты и поэтому в их версиях программ продолжало фигурировать старое значение, непригодное для вычислений по новому алгоритму. Как этого избежать? Readonly поле получает свое значение на этапе выполнения программы, поэтому оно не подвластно описанной слабости.

    Knock Out!



    Со счетом 3:0 победу в сегодняшнем матче одерживает readonly! Я считаю, что довольно справедливо, поскольку этот подход позволяет более гибко использовать понятие константы. Ну а как видно из третьего раунда, оно еще и позволяет избежать трудноуловимых ошибок.

    В качестве заключения я (и не только я) советую использовать readonly вместо const в ваших приложениях. И самый главный совет – используйте константы. Этот тезис относится ко всем языкам программирования, с которыми вы сталкивались или столкнетесь. No magic numbers!