JavaRush /Курси /C# SELF /Часті помилки під час оголошення класів і об’єктів

Часті помилки під час оголошення класів і об’єктів

C# SELF
Рівень 25 , Лекція 0
Відкрита

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 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, "деякі дані");
    }
}

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 може підсвічувати такі випадки. Що менше «мертвих зон», то простіше підтримувати застосунок!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ