1. Проблема совпадающих имён: поле, свойство, параметр
Новички часто сталкиваются с ситуацией, когда имена свойств, параметров конструктора и приватных полей совпадают. В этом случае компилятор не ругается, но результат может оказаться не тем, что вы ожидали.
Пример
public class Student
{
private string name;
public Student(string name)
{
name = name; // "Казалось бы, всё правильно..."
}
public void PrintName()
{
Console.WriteLine(name);
}
}
Что же произойдёт? Методу PrintName() нечего будет печатать: приватное поле name так и останется null! Всё потому, что внутри конструктора name = name; работает с локальным параметром, а не с полем класса.
Как избежать
Используйте ключевое слово this для обращения к полям/свойствам класса:
public Student(string name)
{
this.name = name; // Теперь всё хорошо!
}
Заметка: Rider и другие IDE помогут вам заметить такие ошибки — не игнорируйте их подсказки!
2. Отсутствует конструктор по умолчанию: когда он нужен?
Если вы объявили хотя бы один собственный конструктор, компилятор больше не создаёт "пустой" конструктор по умолчанию. Это часто приводит к ошибкам, особенно при попытке создания объекта без параметров.
Пример
public class Book
{
public string Title { get; set; }
// Явно объявленный конструктор
public Book(string title)
{
Title = title;
}
}
var book = new Book(); // Компилятор: "Бип-бип! Нет конструктора без параметров!"
Если вы собираетесь создавать экземпляр как без параметров, так и с ними, не забудьте явно добавить нужный конструктор:
public Book() { }
public Book(string title)
{
Title = title;
}
3. Модификаторы доступа: приватная жизнь публичных людей
Частая ошибка начинающих — не подумать о модификаторах доступа. По умолчанию поля и методы класса имеют модификатор private, а члены структуры — private. Неявное использование или забывчивость могут привести к невозможности использовать нужные свойства или методы вне класса.
Пример
public class Animal
{
string name; // private по умолчанию
public void PrintName()
{
Console.WriteLine(name);
}
}
var animal = new Animal();
animal.name = "Барсик"; // Ошибка компиляции!
Компилятор не пропустит попытку обращения к приватному члену. Правильнее явно указывать модификатор:
public string Name;
или
private string name;
Для более грамотного доступа используйте свойства:
public string Name { get; set; }
4. Забыли инициализировать поле: ловушка null
Очень распространённая ловушка — поле класса не инициализировано. Это особенно актуально, если поле будет использоваться в методах, вызываемых до явной инициализации.
Пример
public class Cat
{
public string Name;
public void SayHello()
{
Console.WriteLine($"Мяу! Длинна имени {Name.Length} букв!"); // Возможен NullReferenceException
}
}
var cat = new Cat();
cat.SayHello(); // Какая трагедия!
Переменная Name равна null, и попытка обратиться к Length приведёт к NullReferenceException. Особенно тщательно следите за состоянием объектов после создания!
Совет: Используйте конструкторы для обязательного задания важных полей, а свойства помечайте как обязательные (в новых C# - через required).
5. Бесконечная рекурсия в автосвойствах: ошибка копипасты
Бывает, пишем свойство вручную и ошибаемся — например, внутри геттера или сеттера обращаемся к самому себе (а не к приватному полю), что приводит к зацикливанию.
Пример
public class Dog
{
public int Age
{
get { return Age; } // Ой!
set { Age = value; } // Ой-ой!
}
}
Такой код при любом вызове Age сам себя будет вызывать... до бесконечности (пока не закончится стек — рекурсивное самоедство).
Как правильно
Используйте отдельное приватное поле:
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
Либо воспользуйтесь автосвойством:
public int Age { get; set; }
6. Несогласованность имён
В программировании важна аккуратность с регистрами и названиями. C# — язык с учётом регистра (Case-Sensitive), и Name, name, NAME — это три разных идентификатора.
Также, если вы изменили имя поля или свойства, убедитесь, что изменили его во всех местах! Rider поможет отрефакторить код, но не всегда застрахует от опечаток (особенно если есть похожие имена).
7. Использование некорректного модификатора (static/instance)
Иногда новички путают методы экземпляра и статические методы. Проблема появляется, когда вы хотите обратиться к полю/методу не так, как это допускается синтаксисом C#.
Пример
public class MathHelper
{
public static int Add(int a, int b) => a + b;
public int Multiply(int a, int b) => a * b;
}
var sum = MathHelper.Add(2, 3); // OK
var mul = MathHelper.Multiply(2, 3); // Ошибка!
Метод Multiply не статический, его нельзя вызывать через имя класса без создания объекта:
var helper = new MathHelper();
var mul = helper.Multiply(2, 3); // Вот теперь правильно
8. Ошибки при наследовании конструктора
Если ваш класс наследуется от другого класса, не забывайте, что базовый класс может требовать передачи параметров в свой конструктор.
Пример
public class Animal
{
public string Name;
public Animal(string name)
{
Name = name;
}
}
public class Dog : Animal
{
public Dog() { } // Ошибка: базовый класс требует name!
}
Компилятор не даст вам объявить такой конструктор. Нужно явно вызвать конструктор базового класса:
public class Dog : Animal
{
public Dog(string name) : base(name) { }
}
9. Забыл реализовать все члены интерфейса
Если ваш класс реализует интерфейс, но не реализовал все его методы — получите ошибку компиляции: "Класс не реализует интерфейс полностью".
Пример
public interface IWalker
{
void Walk();
void Run();
}
public class Turtle : IWalker
{
public void Walk()
{
Console.WriteLine("Медленно и уверенно...");
}
// Ой! Про Run забыли!
}
IDE будет ругаться, и Turtle не компилируется, пока не реализуете все методы интерфейса.
10. Использование неинициализированных объектов
В C# объекты создаются явно с помощью new. Если вы просто объявили переменную-ссылку, но не присвоили ей объект — она равна null. При попытке обращения к полям/методам получите знаменитый NullReferenceException.
Пример
Student student;
student.PrintName(); // Сюрприз!
Нужно обязательно проинициализировать объект:
Student student = new Student("Василиса");
student.PrintName();
11. Ошибка: public поля вместо свойств
Старый "антипаттерн": объявлять все поля класса как public. Почему это плохо? Поля не защищены, контроль над изменениями отсутствует, инкапсуляция нарушена!
Пример
public class Car
{
public int speed; // Не делайте так!
}
Лучше использовать свойства:
public class Car
{
public int Speed { get; set; }
}
Свойства позволяют добавить проверки, логику, ограничения:
private int speed;
public int Speed
{
get { return speed; }
set
{
if (value < 0) speed = 0;
else speed = value;
}
}
12. Неявное создание "магических чисел" или строк
Если вы видите в коде 42, "Иван", "SomeFile.txt" и прочие "магические" значения прямо в теле методов — это потенциальная ошибка. Лучше использовать константы или поля.
Пример
public class ConfigManager
{
public void Save()
{
File.WriteAllText("config.txt", "some data"); // Магическая строка!
}
}
Лучше:
public class ConfigManager
{
private const string ConfigFileName = "config.txt";
public void Save()
{
File.WriteAllText(ConfigFileName, "some data");
}
}
13. Ошибка копии: переменные-ссылки на один и тот же объект
В C# переменные-объекты — это ссылки. Присваивание одной переменной другой копирует только ссылку, а не сам объект. Это часто приводит к "внезапным" изменениям.
Пример
Student s1 = new Student("Вася");
Student s2 = s1;
s1.Name = "Коля";
Console.WriteLine(s2.Name); // Выведет "Коля"!
Почему? Потому что s1 и s2 — две ссылки на один объект!
14. Отсутствие обратной связи при ошибках
Плохо, когда класс позволяет присвоить недопустимые значения. Например, отрицательный возраст.
Пример
public class Human
{
public int Age { get; set; }
}
var h = new Human();
h.Age = -50; // Без проблем...
Логика класса должна защищать от бессмысленных значений:
private int age;
public int Age
{
get { return age; }
set
{
if (value < 0)
throw new ArgumentException("Возраст не может быть отрицательным.");
age = value;
}
}
15. Путаница области видимости (scope)
Иногда переменные объявляются с одинаковыми именами в разных областях видимости — внутри метода и как поля класса.
Пример
public class MyClass
{
int value = 10;
public void Foo()
{
int value = 99;
Console.WriteLine(value); // Выведет 99, а не 10!
}
}
Чтобы однозначно обратиться к полю класса, используйте this.value.
16. Переопределение методов без ключевого слова override
Когда вы хотите переопределить виртуальный метод базового класса, не забудьте про override!
Пример
public class Animal
{
public virtual void Speak() => Console.WriteLine("Животное говорит");
}
public class Cat : Animal
{
public void Speak() => Console.WriteLine("Мяу!"); // Не override!
}
В данном случае метод базового класса не переопределён: новый метод просто скрывает старый (shadowing), что может привести к неожиданностям при работе с базовыми ссылками.
Правильно:
public override void Speak() => Console.WriteLine("Мяу!");
17. Вызов виртуального метода из конструктора
Это не совсем ошибка компиляции, но часто приводит к трудновыявляемым багам. Когда в конструкторе базового класса вызывается виртуальный метод, а этот метод переопределён в наследнике — в момент вызова могут быть неинициализированы поля дочернего класса.
Пример
public class Animal
{
public Animal()
{
Speak();
}
public virtual void Speak()
{
Console.WriteLine("Животное...");
}
}
public class Cat : Animal
{
public string Name { get; set; } = "Барсик";
public override void Speak()
{
Console.WriteLine($"Мяу! Я {Name}");
}
}
Cat cat = new Cat();
// В момент вызова Speak() конструктор Cat ещё НЕ отработал, Name ещё null!
18. Описание объекта только через конструктор (без свойств)
Если у класса есть только конструктор, а после создания объект нельзя изменить — хорошо для неизменяемых объектов, но не всегда удобно. В большинстве случаев лучше давать возможность пользователю класса изменить свойства (или хотя бы некоторые из них).
19. Игнорирование стандартных соглашений оформления (naming conventions)
Имена классов начинаются с большой буквы, методы — тоже с большой, поля — с маленькой и подчёркиванием (или через camelCase), константы — ВСЕ ЗАГЛАВНЫЕ. Использование единого стиля не только улучшает читаемость, но и снижает риск случайных ошибок!
20. Неиспользуемые поля и методы
Если вы завели поле, но нигде его не используете — это "мертвый код". IDE может подсветить вам такие случаи. Чем меньше "мертвых зон" — тем проще поддерживать приложение!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ