Oop

Властивості та Поля

Вивчіть різницю між полями та властивостями в C#, автоматичні властивості, init accessors, нове ключове слово field та індексатори для ефективної інкапсуляції даних.

Властивості та Поля

Вступ: Проблема Контролю Доступу

Уявіть, що ви створюєте клас для представлення банківського рахунку. Найпростіший підхід — зробити баланс публічним полем:

public class BankAccount
{
    public decimal Balance; // Небезпечно!
}

var account = new BankAccount();
account.Balance = -1000; // Ніхто не заважає встановити негативний баланс!

Проблема очевидна: будь-який код може встановити некоректне значення. Нам потрібен механізм контролю доступу до даних. Саме для цього в C# існують Properties (Властивості) — вони надають інкапсуляцію та дозволяють додавати логіку при читанні або записі даних.

Інкапсуляція (Encapsulation) — один з фундаментальних принципів ООП, який передбачає приховування внутрішньої реалізації об'єкта та надання контрольованого доступу до його стану через публічний інтерфейс. Детальніше про принципи ООП можна прочитати в наступному розділі.

Еволюція: Від Полів до Властивостей

Історично підходи до управління даними в класах еволюціонували:

Loading diagram...
timeline
    title Еволюція управління даними в CSharp
    CSharp 1.0 (2002) : Публічні поля : Properties з backing fields
    CSharp 3.0 (2007) : Auto-implemented properties
    CSharp 6.0 (2015) : Expression-bodied properties : Property initializers
    CSharp 9.0 (2020) : Init-only setters
    CSharp 13.0 (2024) : Field keyword (preview)

Фундаментальні Концепції

Fields (Поля)

Field (Поле) — це змінна, оголошена безпосередньо в класі або структурі. Поля зберігають дані об'єкта.

public class Person
{
    // Приватне поле (backing field)
    private string _firstName;

    // Публічне поле (не рекомендується)
    public int Age;

    // Поле тільки для читання (readonly)
    private readonly DateTime _birthDate;

    // [Константа](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/const)
    private const int MaxAge = 150;
}

Характеристики полів:

ХарактеристикаОпис
Модифікатори доступуprivate, protected, internal, public
ReadonlyМоже бути ініціалізовано тільки при оголошенні або в конструкторі
ConstЗначення визначається на етапі компіляції, неявно static
ВикористанняЗазвичай приватні, як backing fields для властивостей

Properties (Властивості)

Property (Властивість) — це член класу, який надає гнучкий механізм читання, запису або обчислення значення приватного поля через accessors (аксесори): get та set.

public class Person
{
    private string _firstName; // Backing field

    // Властивість з повною реалізацією
    public string FirstName
    {
        get
        {
            return _firstName;
        }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Ім'я не може бути порожнім");

            _firstName = value.Trim();
        }
    }
}

Переваги властивостей над полями:

Валідація: Можна перевіряти дані перед встановленням
Інкапсуляція: Приховування внутрішньої реалізації
Обчислювані значення: Властивість може повертати обчислене значення
Зворотна сумісність: Зміна поля на властивість не ламає API ✅ Підтримка інтерфейсів: Інтерфейси можуть оголошувати властивості, але не поля
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Властивості, на відміну від полів, не є простими змінними. На рівні проміжної мови (Intermediate Language, IL), компілятор перетворює властивість на пару методів: get_PropertyName() та set_PropertyName(). Це означає, що кожне звернення до властивості насправді є викликом методу, що й дозволяє додавати логіку валідації, обчислень чи логування. Поля, навпаки, транслюються в прямі операції з пам'яттю, що робить їх швидшими, але менш гнучкими.

Expression-Bodied Properties

Для простих властивостей можна використовувати скорочений синтаксис:

public class Person
{
    private string _firstName;
    private string _lastName;

    // Read-only властивість з expression body
    public string FullName => $"{_firstName} {_lastName}";

    // Властивість з expression-bodied get/set
    public string FirstName
    {
        get => _firstName;
        set => _firstName = value?.Trim() ?? throw new ArgumentNullException(nameof(value));
    }
}

Порівняння: Fields vs Properties

КритерійFields (Поля)Properties (Властивості)
Синтаксис доступуobj.fieldobj.Property
Валідація❌ Немає✅ Так, в set accessor
Інкапсуляція❌ Пряме зберігання✅ Контрольований доступ
Обчислення❌ Завжди значення✅ Може обчислюватись
Інтерфейси❌ Не підтримуються✅ Підтримуються
ПродуктивністьТрохи швидшеМоже бути незначна різниця
ВикористанняВнутрішнє зберіганняПублічний API
Loading diagram...
flowchart TD
    Start([Потрібно зберегти дані в класі]) --> Question1{Дані будуть<br/>публічними?}
    Question1 -->|Ні| Field[Використовуй<br/>приватне поле]
    Question1 -->|Так| Question2{Потрібна валідація<br/>або логіка?}
    Question2 -->|Ні| AutoProp[Auto-implemented<br/>property]
    Question2 -->|Так| FullProp[Property з<br/>backing field]

    Question2 --> Question3{Тільки<br/>читання?}
    Question3 -->|Так| ReadOnly[Read-only property<br/>або init accessor]

    style Field fill:#64748b,stroke:#334155,color:#ffffff
    style AutoProp fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style FullProp fill:#f59e0b,stroke:#b45309,color:#ffffff
    style ReadOnly fill:#64748b,stroke:#334155,color:#ffffff
    style Question1 fill:#f59e0b,stroke:#b45309,color:#ffffff
    style Question2 fill:#f59e0b,stroke:#b45309,color:#ffffff
    style Question3 fill:#f59e0b,stroke:#b45309,color:#ffffff
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Auto-Implemented Properties

Auto-implemented properties (Автоматично реалізовані властивості) — це синтаксичний цукор, коли компілятор автоматично створює приватне backing field.

Базовий Синтаксис

public class Product
{
    // Auto-implemented property - компілятор створює приховане backing field
    public string Name { get; set; }

    // Еквівалентно:
    // private string <Name>k__BackingField;
    // public string Name
    // {
    //     get => <Name>k__BackingField;
    //     set => <Name>k__BackingField = value;
    // }
}

Варіації Auto-Implemented Properties

public string Name { get; set; }

Приклад використання

public class Book
{
    // Auto-implemented з ініціалізацією
    public string Title { get; set; } = string.Empty;

    // Private setter - зовнішній код не може змінювати
    public string ISBN { get; private set; }

    // Read-only - можна встановити тільки в конструкторі
    public DateTime PublishedDate { get; }

    public Book(string isbn, DateTime publishedDate)
    {
        ISBN = isbn;
        PublishedDate = publishedDate; // OK в конструкторі
    }

    public void UpdateISBN(string newIsbn)
    {
        ISBN = newIsbn; // OK всередині класу
    }
}

// Використання
var book = new Book("978-0-123456-47-2", DateTime.Now);
Console.WriteLine(book.ISBN); // OK - читання
// book.ISBN = "новий"; // ПОМИЛКА - private setter
Best Practice: Використовуйте auto-implemented properties для простих випадків без валідації. Це робить код чистішим і читабельнішим.Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Хоча автоматично реалізовані властивості є надзвичайно зручними, важливо розуміти їхні обмеження. Вони ідеально підходять для сценаріїв, де властивість є простим сховищем даних без додаткової логіки. Як тільки виникає потреба у валідації, логуванні, обчисленні значення "на льоту" або виконанні будь-яких побічних ефектів при читанні чи записі, необхідно переходити до повної реалізації властивості з явним backing field.

Init Accessors (C# 9+)

Проблема: Immutable Objects

До C# 9, створення незмінних об'єктів (immutable objects) було незручним:

public class Person
{
    public string Name { get; } // Тільки конструктор
    public int Age { get; }

    // Треба передавати ВСІ параметри
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

// Незручно для об'єктів з багатьма властивостями
var person = new Person("Іван", 25);

Синтаксис init Accessor

Init accessor дозволяє встановити значення властивості тільки під час ініціалізації об'єкта (в конструкторі або object initializer).

public class ImmutablePerson
{
    public string Name { get; init; }
    public int Age { get; init; }
    public string Email { get; init; }
}

// Використання
var person = new ImmutablePerson
{
    Name = "Іван",
    Age = 25,
    Email = "ivan@example.com"
};

// Після створення - тільки читання
Console.WriteLine(person.Name); // OK
// person.Age = 26; // ПОМИЛКА КОМПІЛЯЦІЇ!

Init з Валідацією

Можна додати валідацію в init accessor:

public class ValidatedPerson
{
    private string _name;
    private int _age;

    public string Name
    {
        get => _name;
        init
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Ім'я не може бути порожнім");
            _name = value.Trim();
        }
    }

    public int Age
    {
        get => _age;
        init
        {
            if (value < 0 || value > 150)
                throw new ArgumentOutOfRangeException(nameof(value),
                    "Вік має бути від 0 до 150");
            _age = value;
        }
    }
}

Порівняння: readonly vs init

Характеристика{ get; } (readonly){ get; init; }
ВстановленняТільки в конструкторіКонструктор + object initializer
Синтаксисnew(param1, param2)new { Prop1 = val1, Prop2 = val2 }
ЗручністьБагато параметрів незручноObject initializer зручніше
ВалідаціяМожливаМожлива
Версія C#Будь-якаC# 9+
Record types в C# 9+ автоматично використовують init accessors для створення незмінних об'єктів з concise syntax. Детальніше про record types можна прочитати в офіційній документації C#.

Незмінність (immutability) є ключовою концепцією функціонального програмування, яка набуває все більшої популярності в об'єктно-орієнтованих мовах, як-от C#. Об'єкти, стан яких неможливо змінити після створення, є більш передбачуваними та безпечними для використання у багатопотокових середовищах, оскільки вони не потребують синхронізації доступу. init аксесори є потужним інструментом для побудови таких незмінних типів, поєднуючи безпеку readonly полів зі зручністю синтаксису ініціалізаторів об'єктів.

Field Keyword (C# 13/14 Preview)

Preview Feature: Ключове слово field знаходиться в режимі preview в C# 13 і планується до офіційного релізу в C# 14. Для використання потрібно увімкнути preview features.

Проблема: Semi-Auto Properties

Іноді потрібна auto-implemented property, але з невеликою логікою (наприклад, trim значення):

public class Person
{
    private string _middleName; // Треба оголошувати backing field

    public string MiddleName
    {
        get => _middleName; // Дублювання
        set => _middleName = value?.Trim(); // Проста логіка
    }
}

Що таке field keyword?

field keyword — це контекстне ключове слово, яке надає доступ до compiler-synthesized backing field у властивостях. Це дозволяє писати semi-auto properties — частково автоматичні властивості.

public class TimeSlot
{
    // Компілятор синтезує backing field автоматично
    public int Hours
    {
        get; // Автоматичний get accessor
        set  // Власна логіка в set
        {
            if (value >= 0 && value <= 23)
            {
                field = value; // Доступ до синтезованого поля
            }
            else
            {
                throw new ArgumentOutOfRangeException(nameof(value),
                    "Години мають бути від 0 до 23");
            }
        }
    }
}

Де можна використовувати field

public class Example
{
    // ✅ set accessor з валідацією
    public int Value
    {
        get;
        set => field = value >= 0 ? value : 0;
    }

    // ✅ init accessor з валідацією
    public string Name
    {
        get;
        init => field = value?.Trim() ?? throw new ArgumentNullException();
    }

    // ✅ Обидва accessors
    public decimal Price
    {
        get => field;
        set => field = Math.Round(value, 2);
    }
}

Увімкнення Preview Features

Щоб використовувати field keyword:

Крок 1: Оновіть .csproj файл

project.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <LangVersion>preview</LangVersion>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
  </PropertyGroup>
</Project>

Крок 2: Використовуйте field у властивостях

public string Email
{
    get;
    set => field = value?.ToLowerInvariant();
}

Порівняльна Таблиця Підходів

ПідхідСинтаксисBacking FieldВалідаціяСкладність
Повна реалізаціяManual get/setОголошується вручну✅ ТакВисока
Auto property{ get; set; }Автоматичне❌ НіНизька
field keywordget; set => field = ...Автоматичне✅ ТакСередня
Best Practice: Використовуйте field keyword для властивостей, де потрібна проста валідація або трансформація, але не хочеться вручну оголошувати backing field.Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Ключове слово field є елегантною відповіддю на давню проблему "багатослівних" властивостей. Раніше розробникам доводилося обирати між абсолютно лаконічною автоматичною властивістю (без логіки) та повною реалізацією з явним backing field (навіть для найпростішої логіки, як-от INotifyPropertyChanged). field заповнює цю прогалину, дозволяючи додавати логіку до аксесорів, не жертвуючи при цьому лаконічністю, оскільки компілятор все ще генерує поле для зберігання даних за лаштунками. Це робить код чистішим і зменшує кількість шаблонного коду.

Indexers (Індексатори)

Indexer (Індексатор) — це властивість, яка дозволяє індексувати об'єкти як масиви або колекції, використовуючи синтаксис [].

Подібно до властивостей, індексатори є синтаксичним цукром. На рівні IL-коду компілятор перетворює індексатор на методи, які зазвичай називаються get_Item та set_Item, що приймають індекс як параметр. Це дозволяє класам імітувати поведінку масивів або словників, надаючи при цьому повний контроль над внутрішньою логікою доступу до даних.

Базовий Синтаксис

public class WeekTemperatures
{
    private float[] _temperatures = new float[7];

    // Indexer - використовує ключове слово 'this'
    public float this[int day]
    {
        get
        {
            if (day < 0 || day > 6)
                throw new ArgumentOutOfRangeException(nameof(day));
            return _temperatures[day];
        }
        set
        {
            if (day < 0 || day > 6)
                throw new ArgumentOutOfRangeException(nameof(day));
            _temperatures[day] = value;
        }
    }
}

// Використання
var week = new WeekTemperatures();
week[0] = 22.5f; // Понеділок
week[1] = 24.0f; // Вівторок
Console.WriteLine($"Температура в понеділок: {week[0]}°C");

Read-Only Indexer

public class FibonacciSequence
{
    // Тільки для читання - expression-bodied
    public int this[int index] => index switch
    {
        0 => 0,
        1 => 1,
        _ => this[index - 1] + this[index - 2]
    };
}

var fib = new FibonacciSequence();
Console.WriteLine(fib[10]); // 55

Індексатори з Різними Типами

public class PhoneBook
{
    private Dictionary<string, string> _contacts = new();

    // Індексатор з string ключем
    public string this[string name]
    {
        get => _contacts.TryGetValue(name, out var phone) ? phone : "Не знайдено";
        set => _contacts[name] = value;
    }
}

var book = new PhoneBook();
book["Іван"] = "+380123456789";
Console.WriteLine(book["Іван"]); // +380123456789

Індексатори з Багатьма Параметрами

public class Matrix
{
    private int[,] _data = new int[3, 3];

    // Багатовимірний індексатор
    public int this[int row, int col]
    {
        get
        {
            ValidateIndices(row, col);
            return _data[row, col];
        }
        set
        {
            ValidateIndices(row, col);
            _data[row, col] = value;
        }
    }

    private void ValidateIndices(int row, int col)
    {
        if (row < 0 || row > 2 || col < 0 || col > 2)
            throw new ArgumentOutOfRangeException();
    }
}

var matrix = new Matrix();
matrix[0, 0] = 1;
matrix[1, 1] = 5;
Console.WriteLine(matrix[0, 0]); // 1

Індексатори в Інтерфейсах

public interface IDataStore<T>
{
    T this[int index] { get; set; } // Оголошення в інтерфейсі
}

::tip
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: [Класи та Об'єкти](./2.classes-objects.md).
::

public class DataStore<T> : IDataStore<T>
{
    private T[] _data = new T[100];

    public T this[int index]
    {
        get => _data[index];
        set => _data[index] = value;
    }
}

Приклад: Досконала Колекція

public class SmartList<T>
{
    private List<T> _items = new();

    // Індексатор з валідацією
    public T this[int index]
    {
        get
        {
            if (index < 0 || index >= _items.Count)
                throw new IndexOutOfRangeException(
                    $"Індекс {index} поза межами [0, {_items.Count - 1}]");
            return _items[index];
        }
        set
        {
            if (index < 0 || index >= _items.Count)
                throw new IndexOutOfRangeException(
                    $"Індекс {index} поза межами [0, {_items.Count - 1}]");
            _items[index] = value;
        }
    }

    // Індексатор з іменованим доступом (string)
    public T this[string selector]
    {
        get => selector.ToLower() switch
        {
            "first" => _items.FirstOrDefault(),
            "last" => _items.LastOrDefault(),
            _ => throw new ArgumentException($"Невідомий селектор: {selector}")
        };
    }

    public void Add(T item) => _items.Add(item);
    public int Count => _items.Count;
}
Loading diagram...
classDiagram
    class SmartList~T~ {
        -List~T~ _items
        +T this[int index]
        +T this[string selector]
        +Add(T item)
        +int Count
    }

    class Client {
        +UseSmartList()
    }

    Client --> SmartList : використовує

    note for SmartList "Підтримує 2 індексатори:\n1. За числовим індексом\n2. За іменованим селектором"

    style SmartList fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style Client fill:#64748b,stroke:#334155,color:#ffffff

Візуалізація: Property Lifecycle

Loading diagram...
sequenceDiagram
    participant Client as Клієнтський Код
    participant Prop as Property (get/set)
    participant Field as Backing Field

    Client->>Prop: person.Age = 25
    activate Prop
    Prop->>Prop: Валідація значення
    alt Значення коректне
        Prop->>Field: Зберегти значення
        Field-->>Prop: OK
        Prop-->>Client: Встановлено
    else Значення некоректне
        Prop-->>Client: throw Exception
    end
    deactivate Prop

    Client->>Prop: var age = person.Age
    activate Prop
    Prop->>Field: Читання значення
    Field-->>Prop: Повернути значення
    Prop-->>Client: Повернути age
    deactivate Prop

Практичні Приклади

Приклад 1: Клас Product з Валідацією

public class Product
{
    private decimal _price;
    private int _quantity;

    public string Name { get; init; } = string.Empty;

    public decimal Price
    {
        get => _price;
        set
        {
            if (value < 0)
                throw new ArgumentException("Ціна не може бути від'ємною");
            _price = Math.Round(value, 2); // Округлення до 2 знаків
        }
    }

    public int Quantity
    {
        get => _quantity;
        set
        {
            if (value < 0)
                throw new ArgumentException("Кількість не може бути від'ємною");
            _quantity = value;
        }
    }

    // Обчислювана властивість
    public decimal TotalValue => Price * Quantity;

    // Expression-bodied read-only
    public bool IsInStock => Quantity > 0;
}

Приклад 2: Конфігурація з field keyword

public class AppConfiguration
{
    // Semi-auto property з нормалізацією
    public string ServerUrl
    {
        get;
        set => field = value?.TrimEnd('/').ToLowerInvariant()
            ?? throw new ArgumentNullException(nameof(value));
    }

    // Валідація діапазону
    public int MaxConnections
    {
        get;
        set => field = value is >= 1 and <= 1000
            ? value
            : throw new ArgumentOutOfRangeException(nameof(value));
    }

    // Init з валідацією
    public int Port
    {
        get;
        init => field = value is >= 1 and <= 65535
            ? value
            : throw new ArgumentOutOfRangeException(nameof(value));
    }
}

Troubleshooting: Типові Помилки

Помилка 1: Публічні Поля Замість Властивостей

Анти-паттерн: Використання публічних полів
public class User
{
    public string Email; // Публічне поле - немає валідації
}

var user = new User();
user.Email = "invalid"; // Некоректний email!
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Помилка 2: Забута Валідація

// ❌ Небезпечно
public class BankAccount
{
    public decimal Balance { get; set; } // Можна встановити від'ємне!
}

// ✅ Безпечно
public class BankAccount
{
    private decimal _balance;

    public decimal Balance
    {
        get => _balance;
        private set // Тільки клас може змінювати напряму
        {
            if (value < 0)
                throw new InvalidOperationException("Баланс не може бути від'ємним");
            _balance = value;
        }
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Сума має бути додатною");
        Balance += amount;
    }

    public void Withdraw(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Сума має бути додатною");
        if (Balance - amount < 0)
            throw new InvalidOperationException("Недостатньо коштів");
        Balance -= amount;
    }
}
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.
Рішення: Додайте в .csproj:
<LangVersion>preview</LangVersion>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Помилка 3: Неправильне Використання init vs set

// ❌ Проблема: init дозволяє змінювати після створення через with
public class Config
{
    public string ApiKey { get; init; } // Здається незмінним
}

var config = new Config { ApiKey = "secret123" };
var modified = config with { ApiKey = "hacked!" }; // Упс!

// ✅ Рішення: readonly для справжньої незмінності
public class Config
{
    public string ApiKey { get; }

    public Config(string apiKey)
    {
        ApiKey = apiKey;
    }
}

Помилка 4: Preview Features Без Увімкнення

// ❌ Помилка компіляції: CS9258
public class Example
{
    public string Name
    {
        get;
        set => field = value?.Trim(); // 'field' unavailable
    }
}
Рішення: Додайте в .csproj:
<LangVersion>preview</LangVersion>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Практичні Завдання

Рівень 1: Базові Властивості

Завдання 1.1

Створіть клас Student з наступними auto-implemented properties:

  • FirstName (string, get/set)
  • LastName (string, get/set)
  • StudentId (int, get, private set)
  • EnrollmentDate (DateTime, get only)

Додайте властивість FullName, яка повертає FirstName + " " + LastName.

Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Рівень 2: Init Accessors та Валідація

Завдання 2.1

Створіть клас EmailMessage з наступними вимогами:

  • Властивості To, Subject, Body мають використовувати init accessor
  • Додайте валідацію:
    • To має містити @
    • Subject не може бути порожнім і має максимум 100 символів
    • Body не може бути null
  • Додайте read-only властивість CreatedAt, яка зберігає час створення
Для глибшого розуміння цієї теми рекомендуємо ознайомитись з попереднім матеріалом: Класи та Об'єкти.

Рівень 3: Indexers та Field Keyword

Завдання 3.1

Створіть клас ShoppingCart з:

  1. Індексатором для доступу до товарів за назвою
  2. Властивість TotalItems (кількість унікальних товарів)
  3. Властивість TotalPrice (загальна вартість)
  4. Використайте field keyword для властивості DiscountPercent з валідацією (0-100)

Завдання 3.2: Двовимірний Індексатор

Створіть клас Spreadsheet, який реалізує:

  • Двовимірний індексатор [row, col] для доступу до комірок
  • Зберігання даних у словнику (щоб не витрачати пам'ять на порожні комірки)
  • Метод GetUsedCells() який повертає список координат заповнених комірок

Резюме

У цьому розділі ви вивчили:

Fields vs Properties: Поля для внутрішнього зберігання, властивості для публічного API
Auto-implemented Properties: Синтаксичний цукор для простих властивостей
Init Accessors: Створення immutable об'єктів з зручним синтаксисом
Field Keyword: Semi-auto properties з доступом до backing field (C# 13+)
Indexers: Надання array-like доступу до даних об'єкта
Best Practices: Завжди використовуйте властивості для публічних даних з валідацієюНаступні Кроки: У наступному розділі ви дізнаєтеся про методи, делегати та події, які дозволяють визначати поведінку класів та реалізовувати шаблон спостерігача (Observer pattern).

Наступні Кроки: У наступному розділі ви дізнаєтеся про методи, делегати та події, які дозволяють визначати поведінку класів та реалізовувати шаблон спостерігача (Observer pattern).