Инициализация автосвойств в C#: ТОП-5 ошибок и мастер-класс

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему вопрос с автосвойствами собрал тысячи просмотров?

Если вы когда-нибудь писали на C#, то наверняка сталкивались с простым, но важным вопросом: "Как правильно задать начальное значение автосвойству?". Этот вопрос на Stack Overflow собрал десятки тысяч просмотров, и не зря. Кажущаяся простота скрывает множество подводных камней.

Представьте: вы создаете класс User, и хотите, чтобы свойство Name по умолчанию было не null, а пустой строкой. Казалось бы, что может пойти не так? Но на практике разработчики допускают типичные ошибки инициализации C# свойств, которые приводят к NullReferenceException или неочевидному поведению.

В этой статье мы разберем ТОП-5 ошибок и покажем, как их избежать. Вы узнаете современные подходы — от C# 6 до C# 12 — и поймете, какой способ выбрать для вашего проекта.


Ошибка №1: Игнорирование инициализации автосвойств

Самая распространенная ошибка — не задавать начальное значение для ссылочных типов. Многие новички пишут:

public class User
{
    public string Name { get; set; } // null по умолчанию
    public List<string> Roles { get; set; } // null
}

В C# автосвойства для ссылочных типов по умолчанию равны null. Это приводит к проблемам с NullReferenceException, когда вы забываете присвоить значение перед использованием.

Как исправить?

Всегда явно задавайте инициализатор автосвойства C#. Самый простой способ — использовать синтаксис C# 6:

public class User
{
    public string Name { get; set; } = string.Empty;
    public List<string> Roles { get; set; } = new List<string>();
}

Совет: Используйте string.Empty вместо пустых кавычек — это улучшает читаемость и соответствует лучшим практикам C#.


Ошибка №2: Инициализация в конструкторе без учета цепочки вызовов

Другая частая ошибка — установка значения по умолчанию C# только в конструкторе, при этом игнорируя инициализаторы свойств. Это может привести к путанице, особенно при наследовании.

public class BaseClass
{
    public int Value { get; set; }

    public BaseClass()
    {
        Value = 10;
    }
}

public class DerivedClass : BaseClass
{
    public DerivedClass()
    {
        // Value уже = 10, но мы можем перезаписать
    }
}

Проблема: если вы добавите инициализатор в свойство Value и оставите присвоение в конструкторе, то порядок выполнения может вас удивить. Инициализаторы выполняются до тела конструктора базового класса, но после вызова конструктора производного класса.

Как избежать?

Выберите один способ: либо инициализатор, либо конструктор. Лучше отдать предпочтение инициализаторам автосвойств C#, так как они более предсказуемы и лаконичны.

public class BaseClass
{
    public int Value { get; set; } = 10; // Единый источник истины
}

Ошибка №3: Использование readonly полей вместо автосвойств

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

public class Config
{
    public readonly int MaxRetries = 3; // Ошибка: нарушение инкапсуляции
}

Это плохая практика. Поля нарушают инкапсуляцию — вы не сможете добавить логику в getter/setter без изменения API. Кроме того, при сериализации или использовании в WPF/Blazor это создаст проблемы.

Правильный подход

Используйте автосвойства с инициализацией или init-only свойства C# 9:

public class Config
{
    public int MaxRetries { get; init; } = 3; // init — только при инициализации
}

Свойство init позволяет задать значение только в инициализаторе или конструкторе, но не изменять его после создания объекта. Это идеально для инициализации свойств по умолчанию C# в DTO и моделях.


Ошибка №4: Сложная инициализация в конструкторе при наличии зависимостей

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

public class ReportGenerator
{
    public DateTime CreatedAt { get; set; }
    public string ReportName { get; set; }

    public ReportGenerator()
    {
        CreatedAt = DateTime.UtcNow;
        ReportName = $"Report_{CreatedAt:yyyyMMdd}";
    }
}

Это работает, но нарушает принцип единой ответственности. Если логика инициализации станет сложнее, конструктор раздуется.

Современное решение: первичные конструкторы C# 12

C# 12 представил primary constructors, которые позволяют объединить объявление свойств и их инициализацию:

public class ReportGenerator(DateTime createdAt)
{
    public DateTime CreatedAt { get; set; } = createdAt;
    public string ReportName { get; set; } = $"Report_{createdAt:yyyyMMdd}";
}

// Использование:
var report = new ReportGenerator(DateTime.UtcNow);

Этот подход делает код чище и явно показывает зависимости класса. Однако помните: первичные конструкторы — это все еще конструкторы, и они не заменяют инициализаторы автосвойств C#, а дополняют их.


Ошибка №5: Забыть про null-контекст и nullable reference types

С появлением nullable reference types в C# 8, компилятор начал предупреждать о потенциальных null. Но многие игнорируют это, когда инициализируют свойства:

public class Product
{
    public string Title { get; set; } // Warning: non-nullable property is uninitialized
}

Компилятор выдаст предупреждение CS8618. Это не ошибка, но сигнал, что свойство может быть null во время выполнения.

Как правильно?

Используйте инициализацию автосвойств C# вместе с nullable-аннотациями:

#nullable enable

public class Product
{
    public string Title { get; set; } = string.Empty; // OK
    public string? Description { get; set; } // nullable — может быть null
}

Этот подход гарантирует, что Title никогда не будет null, а Description может быть null — и ком

Блоги

Рекомендаций по книгам