Переменные, var, const и readonly, преобразования типов
↑ 1.3 C♯: основы языка · ← Предыдущая · → Следующая
Зачем это на собесе
constvsreadonly, неявные и явные преобразования,checked,decimalдля денег — частые вопросы. Ошибки здесь дают баги с округлением и переполнением.
Объяснение
var
Тип выводится компилятором из правой части на этапе компиляции — это по-прежнему строгая статическая типизация (не dynamic).
var patients = new List<Patient>(); // тип List<Patient>
var total = 0m; // decimal
// var x; // ошибка: нужен инициализаторИспользуйте, когда тип очевиден справа. Явный тип — когда он неочевиден (var result = Calculate();).
const и readonly
const— константа времени компиляции: только примитивы и строки, значение вшивается в IL в месте использования. Неявно static.readonlyполе — присваивается в объявлении или в конструкторе, дальше не меняется. Значение вычисляется в рантайме, может быть любого типа.static readonly— одно значение на тип, вычисляется при инициализации типа.
public class ReportSettings
{
public const int MaxPages = 500; // вшивается в вызывающий код
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30); // в рантайме
private readonly ILogger _log; // только в конструкторе
public ReportSettings(ILogger log) => _log = log;
}Warning
Ловушка
constмежду сборками: если библиотека поменялаpublic const, а зависимая сборка не пересобрана, в ней останется старое значение. Для публичных «констант», которые могут меняться, используйтеstatic readonly.
readonly на поле ссылочного типа запрещает менять ссылку, но не содержимое объекта: readonly List<int> можно дополнять.
Числовые типы
- Целые:
byte,short,int(32 бита),long(64), беззнаковыеuint,ulong. - С плавающей точкой:
float,double— двоичные, неточные для десятичных дробей (0.1 + 0.2 != 0.3). decimal— 128-битный десятичный, точный для денег и финансов, медленнее.
0.1 + 0.2 == 0.3 // false (double)
0.1m + 0.2m == 0.3m // true (decimal)Преобразования
- Неявные — без потери данных:
int → long,int → double. - Явные (cast) — с возможной потерей:
(int)3.9→ 3 (отбрасывание дробной части),(int)longValue— усечение. - Переполнение по умолчанию молча «заворачивается» (
int.MaxValue + 1=int.MinValue).checkedбросаетOverflowException.
int big = int.MaxValue;
int wrapped = big + 1; // -2147483648
int safe = checked(big + 1); // OverflowException
int.Parse("42"); // исключение при ошибке
if (int.TryParse(input, out var n)) { /* ... */ } // без исключений — для пользовательского ввода
Convert.ToInt32(null); // 0 (а int.Parse(null) — исключение)
decimal.Parse("1,5", new CultureInfo("ru-RU")); // учитывайте культуру!
Math.Round(2.5); // 2 — банковское округление (к чётному) по умолчанию!
Math.Round(2.5, MidpointRounding.AwayFromZero); // 3Операторы is, as и приведение ссылочных типов
object o = GetEntity();
if (o is Patient p) Console.WriteLine(p.Name); // pattern matching: проверка + приведение
var doc = o as DocumentReference; // null, если не тот тип (без исключения)
var enc = (Encounter)o; // InvalidCastException, если не тот типНюансы и подводные камни
- Деньги в
double— баг. Толькоdecimal(иnumericв PostgreSQL). Math.Roundпо умолчанию банковское: для отчётов с «школьным» округлением указывайтеMidpointRounding.AwayFromZero.ParseбезCultureInfoзависит от культуры сервера: в контейнере это InvariantCulture, у разработчика — ru-RU. Для машинных форматов —CultureInfo.InvariantCulture.dynamicотключает проверки компилятора: ошибки только в рантайме, медленнее. Нужен редко (COM, динамический JSON).- Целочисленное деление:
5 / 2 == 2. Нужно5 / 2.0или приведение.
Тестирование
[Theory]
[InlineData("1,5", 1.5)]
[InlineData("2", 2)]
public void Parses_russian_decimal(string input, decimal expected) =>
decimal.Parse(input, new CultureInfo("ru-RU")).Should().Be(expected);Вопросы с ответами
Чем const отличается от readonly?
const — значение времени компиляции (только примитивы и строки), вшивается в код вызывающих сборок, неявно static. readonly — присваивается при объявлении или в конструкторе, вычисляется в рантайме, может быть любого типа и быть экземплярным.
Почему для денег используют decimal?
double и float двоичные и не могут точно представить многие десятичные дроби, отсюда ошибки округления. decimal хранит десятичную мантиссу и точен для финансовых расчётов.
Что будет при int.MaxValue + 1?
По умолчанию переполнение без ошибки: int.MinValue. В контексте checked (или при включённой проверке в проекте) — OverflowException.
Чем is и as отличаются от явного приведения?
Явное приведение бросает InvalidCastException при несовпадении типа. as возвращает null (только для ссылочных и nullable). is проверяет тип и в pattern matching сразу даёт типизированную переменную.
var — это динамическая типизация?
Нет. Тип выводится при компиляции и фиксируется. Динамическая — это dynamic.
Связанные темы
- Предыдущая: Типы: value и reference, struct и class, CTS · Следующая: Строки: иммутабельность, StringBuilder, интерполяция, сравнение
- Pattern matching: Pattern matching и switch expressions
- Кодировки и культура: Кодировки, часовые пояса, время на сервере
- Числа в JS: Типы данных и приведение типов