Переменные, var, const и readonly, преобразования типов

↑ 1.3 C♯: основы языка · ← Предыдущая · → Следующая

Обязательно~6 мин чтенияУровень: junior

Зачем это на собесе

const vs readonly, неявные и явные преобразования, 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);

Вопросы с ответами

Связанные темы