Advanced Core

Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)

Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)

Передумови: Рекомендується ознайомитись з класами та об'єктами, базовими інтерфейсами та generics перед вивченням цього розділу.

Навіщо це потрібно?

Уявіть, що ви розробляєте бібліотеку для роботи з логуванням. У вас є інтерфейс ILogger:

public interface ILogger
{
    void Log(string message);
}

Ваша бібліотека використовується в сотнях проєктів. Але тепер вам потрібно додати метод LogError. Проблема: якщо ви просто додасте його до інтерфейсу, всі існуючі реалізації зламаються! 💥

public interface ILogger
{
    void Log(string message);
    void LogError(string error); // ❌ Breaking change!
}

Або інша ситуація: ваш клас повинен реалізувати два інтерфейси з однаковими іменами методів:

interface IPrinter
{
    void Print();
}

interface IDocumentExporter
{
    void Print();
}

class Document : IPrinter, IDocumentExporter
{
    public void Print() // ❓ Який Print реалізується?
    {
        Console.WriteLine("Printing...");
    }
}

Сучасний C# (8+) надає рішення:

  • Explicit Interface Implementation — чітке розмежування реалізацій
  • Default Interface Methods — додавання методів без breaking changes
  • Interface Inheritance — побудова складних ієрархій
  • Static Abstract Members — generic математика та операції

Еволюція Інтерфейсів у C#

Loading diagram...
timeline
    title Еволюція інтерфейсів у C#
    section C# 1.0-7.3
        Базові інтерфейси : Тільки сигнатури методів
        Explicit Implementation : Розв'язання конфліктів імен
    section C# 8.0
        Default Interface Methods : Методи за замовчуванням
        Interface Members Access : Private, protected модифікатори
    section C# 11.0
        Static Abstract Members : Generic math patterns
        Static Virtual Members : Операції у generic типах

Що нового?

Версія C#МожливістьОпис
C# 1.0Basic InterfacesСигнатури методів та властивостей
C# 1.0Explicit ImplementationЯвна реалізація членів інтерфейсу
C# 8.0Default Interface MethodsМетоди з реалізацією за замовчуванням
C# 8.0Access Modifiers in Interfacesprivate, protected, internal
C# 11.0Static Abstract MembersСтатичні абстрактні методи та властивості
C# 11.0Static Virtual MembersОперації та оператори у generic інтерфейсах

Explicit Interface Implementation

Explicit Interface Implementation (явна реалізація інтерфейсу) — це техніка, коли члени інтерфейсу реалізуються з повною кваліфікацією (InterfaceName.MemberName).

Теоретичні аспекти реалізації:

Під капотом, явна реалізація інтерфейсу створює метод у класі, який не має публічного імені, але доступний через віртуальну таблицю методів (vtable) інтерфейсу. Коли компілятор бачить явну реалізацію, він:

  1. Створює метод з внутрішнім іменем, яке містить інформацію про інтерфейс
  2. Реєструє цей метод у vtable інтерфейсу
  3. Забезпечує, що метод не буде доступний через ім'я класу
  4. Дозволяє доступ до методу тільки через інтерфейсний посилання

Це досягається через використання манґлінгу імен (name mangling) у внутрішній реалізації компілятора. CLR використовує ці манґловані імена для правильного виклику методів при роботі з інтерфейсами.

Коли це потрібно?

Сценарій 1: Конфлікт імен

Два інтерфейси мають методи з однаковими іменами:

interface IEnglishDimensions
{
    double Length { get; set; }
    double Width { get; set; }
}

interface IMetricDimensions
{
    double Length { get; set; }
    double Width { get; set; }
}

public class Box : IEnglishDimensions, IMetricDimensions
{
    // Explicit implementation для англійських одиниць
    double IEnglishDimensions.Length { get; set; }
    double IEnglishDimensions.Width { get; set; }

    // Explicit implementation для метричних одиниць
    double IMetricDimensions.Length { get; set; }
    double IMetricDimensions.Width { get; set; }

    // Використання
    public void DisplayDimensions()
    {
        IEnglishDimensions englishBox = this;
        IMetricDimensions metricBox = this;

        englishBox.Length = 12; // дюйми
        metricBox.Length = 30;  // сантиметри

        Console.WriteLine($"English: {englishBox.Length} inches");
        Console.WriteLine($"Metric: {metricBox.Length} cm");
    }
}

Сценарій 2: Приховування деталей реалізації

public interface IRepository<T>
{
    void Add(T item);
    void Remove(T item);
}

public class CustomerRepository : IRepository<Customer>
{
    private List<Customer> _customers = new();

    // Explicit implementation — метод доступний тільки через інтерфейс
    void IRepository<Customer>.Add(Customer item)
    {
        _customers.Add(item);
        Console.WriteLine($"Added {item.Name} to repository");
    }

    void IRepository<Customer>.Remove(Customer item)
    {
        _customers.Remove(item);
        Console.WriteLine($"Removed {item.Name} from repository");
    }

    // Public метод для бізнес-логіки
    public void AddCustomerWithValidation(Customer customer)
    {
        if (string.IsNullOrWhiteSpace(customer.Name))
        {
            throw new ArgumentException("Customer name is required");
        }

        ((IRepository<Customer>)this).Add(customer);
    }
}

// Використання
var repo = new CustomerRepository();
// repo.Add(customer); // ❌ Compile error! Метод недоступний

IRepository<Customer> interfaceRepo = repo;
interfaceRepo.Add(new Customer { Name = "Alice" }); // ✅ OK

Синтаксис: Implicit vs Explicit

public interface IDrawable
{
    void Draw();
}

public class Circle : IDrawable
{
    // Неявна реалізація — метод доступний публічно
    public void Draw()
    {
        Console.WriteLine("Drawing a circle");
    }
}

// Використання
Circle circle = new Circle();
circle.Draw(); // ✅ Можна викликати напряму

IDrawable drawable = circle;
drawable.Draw(); // ✅ Також працює
Loading diagram...
graph TD
    A[Клас з інтерфейсом] --> B{Тип реалізації}
    B -->|Implicit| C[public void Method]
    B -->|Explicit| D[void IInterface.Method]

    C --> E[Доступ через клас ✓]
    C --> F[Доступ через інтерфейс ✓]

    D --> G[Доступ через клас ✗]
    D --> H[Доступ через інтерфейс ✓]

    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style C fill:#64748b,stroke:#334155,color:#ffffff
    style D fill:#64748b,stroke:#334155,color:#ffffff
    style E fill:#22c55e,stroke:#16a34a,color:#ffffff
    style F fill:#22c55e,stroke:#16a34a,color:#ffffff
    style G fill:#ef4444,stroke:#dc2626,color:#ffffff
    style H fill:#22c55e,stroke:#16a34a,color:#ffffff

Множинна Реалізація Інтерфейсів

interface IControl
{
    void Paint();
}

interface ISurface
{
    void Paint();
}

class SampleClass : IControl, ISurface
{
    // Explicit implementation для IControl
    void IControl.Paint()
    {
        Console.WriteLine("IControl.Paint");
    }

    // Explicit implementation для ISurface
    void ISurface.Paint()
    {
        Console.WriteLine("ISurface.Paint");
    }
}

// Використання
SampleClass obj = new SampleClass();

IControl control = obj;
control.Paint(); // Output: IControl.Paint

ISurface surface = obj;
surface.Paint(); // Output: ISurface.Paint

Troubleshooting

Compiler Error CS0538: Явна реалізація можлива тільки для членів інтерфейсів, а не класів.
public class MyClass
{
    public void G() { }
}

class C : MyIFace
{
    void MyClass.G() // ❌ CS0538, MyClass — це клас, не інтерфейс!
    {
    }
}
Виправлення: Використовуйте тільки інтерфейси для явної реалізації.
Best Practice: Використовуйте explicit implementation коли:
  • Потрібно розв'язати конфлікт імен між інтерфейсами
  • Хочете приховати деталі реалізації від публічного API
  • Реалізуєте utility інтерфейси (наприклад, IDisposable), які не є частиною основного API класу
Використовуйте implicit implementation для основного API вашого класу.

Default Interface Methods (C# 8)

Default Interface Methods (методи інтерфейсу за замовчуванням) — революційна можливість C# 8, яка дозволяє додавати методи з реалізацією безпосередньо в інтерфейс.

Проблема: Еволюція API

До C# 8: Додавання нового методу в інтерфейс = breaking change

// Версія 1.0 бібліотеки
public interface ILogger
{
    void Log(string message);
}

// 1000 класів реалізують цей інтерфейс...

// Версія 2.0 — потрібен новий метод
public interface ILogger
{
    void Log(string message);
    void LogError(string error); // ❌ Breaking change!
    // Всі 1000 класів тепер не компілюються!
}

Рішення з C# 8: Default implementation

public interface ILogger
{
    void Log(string message);

    // Default implementation — не ламає існуючий код!
    void LogError(string error)
    {
        Log($"ERROR: {error}");
    }
}

// Існуючі реалізації продовжують працювати
public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }

    // LogError автоматично доступний через default implementation!
}

// Використання
ILogger logger = new ConsoleLogger();
logger.Log("Info message");
logger.LogError("Something went wrong"); // Output: ERROR: Something went wrong

Теоретичні аспекти Default Interface Methods:

Під капотом, default interface methods реалізовані через віртуальні таблиці (vtables) у CLR. Коли компілятор бачить default метод в інтерфейсі, він:

  1. Додає метод до віртуальної таблиці інтерфейсу
  2. Для класів, що реалізують інтерфейс без перевизначення методу, використовується реалізація за замовчуванням
  3. Якщо клас перевизначає метод, він замінює посилання у vtable на свою реалізацію

Це дозволяє забезпечити сумісність з існуючим кодом, оскільки існуючі класи автоматично отримують нову функціональність без необхідності змінювати їх реалізацію.

Синтаксис та Можливості

public interface ICustomer
{
    // Звичайні члени (без реалізації)
    string Name { get; set; }
    int Id { get; }

    // Default method з реалізацією
    string GetDisplayName()
    {
        return $"{Name} (ID: {Id})";
    }

    // Default method з логікою
    bool IsValid()
    {
        return !string.IsNullOrWhiteSpace(Name) && Id > 0;
    }

    // Default property (C# 8+)
    string Status => "Active"; // Read-only property з значенням
}

Override Default Methods

Класи можуть перевизначити default implementation:

public interface ILogger
{
    void Log(string message);

    void LogError(string error)
    {
        Log($"ERROR: {error}");
    }
}

public class FileLogger : ILogger
{
    public void Log(string message)
    {
        File.AppendAllText("log.txt", message + "\n");
    }

    // Override default implementation з кращою логікою
    public void LogError(string error)
    {
        string errorMessage = $"[{DateTime.Now}] CRITICAL ERROR: {error}";
        File.AppendAllText("errors.txt", errorMessage + "\n");
        Log(errorMessage); // Також логуємо в основний файл
    }
}

Diamond Problem

Diamond Problem виникає, коли клас реалізує два інтерфейси, які успадковують спільний базовий інтерфейс з default methods.

Loading diagram...
graph TD
    A[IBase<br/>void Method - default impl] --> B[ILeft<br/>override Method]
    A --> C[IRight<br/>override Method]
    B --> D[MyClass]
    C --> D
    D --> E{Який Method викликається?}

    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B fill:#64748b,stroke:#334155,color:#ffffff
    style C fill:#64748b,stroke:#334155,color:#ffffff
    style D fill:#f59e0b,stroke:#b45309,color:#ffffff
    style E fill:#ef4444,stroke:#dc2626,color:#ffffff

Приклад проблеми:

public interface IBase
{
    void Print() => Console.WriteLine("IBase.Print");
}

public interface ILeft : IBase
{
    void IBase.Print() => Console.WriteLine("ILeft.Print"); // Override
}

public interface IRight : IBase
{
    void IBase.Print() => Console.WriteLine("IRight.Print"); // Override
}

// ❌ Compile Error CS8705:Interface member does not have a most specific implementation
public class MyClass : ILeft, IRight
{
}

Розв'язання: Клас повинен явно вказати реалізацію:

public class MyClass : ILeft, IRight
{
    public void Print()
    {
        Console.WriteLine("MyClass.Print — manually resolved");
    }
}

// Або використовуючи explicit implementation
public class MyClass2 : ILeft, IRight
{
    void IBase.Print()
    {
        // Обрати одну з реалізацій
        ((ILeft)this).Print(); // Або ((IRight)this).Print()
    }
}

Access Modifiers у Default Methods

Default methods можуть мати різні рівні доступу:

public interface IAdvanced
{
    // Public method (за замовчуванням)
    void PublicMethod() => Helper();

    // Protected method — доступний тільки в класах, що реалізують інтерфейс
    protected void Helper()
    {
        Console.WriteLine("Helper method");
    }

    // Private method — тільки всередині інтерфейсу
    private static void Validate()
    {
        Console.WriteLine("Validation logic");
    }
}

Порівняння з Abstract Class

КритерійAbstract ClassInterface з Default Methods
Множинне успадкування❌ Не підтримується✅ Клас може реалізувати багато
Поля (fields)✅ Можуть мати❌ Тільки статичні
Constructors✅ Можуть мати❌ Не можуть
Default implementation✅ Virtual methods✅ Default methods
Access modifiers✅ Всі рівні доступу✅ public, private, protected
State (стан)✅ Instance fields❌ Немає стану
Коли використовувати Default Interface Methods:
  • ✅ Еволюція публічних API без breaking changes
  • ✅ Надання utility methods для інтерфейсів
  • ✅ Коли потрібна множинна реалізація
Коли використовувати Abstract Class:
  • ✅ Потрібен спільний стан (fields)
  • ✅ Потрібні constructors
  • ✅ Складна базова логіка

Interface Inheritance

Interface Inheritance (успадкування інтерфейсів) дозволяє створювати ієрархії інтерфейсів, де один інтерфейс розширює можливості іншого.

Базове Успадкування

// Базовий інтерфейс
public interface IReadable
{
    string Read();
}

// Розширений інтерфейс — успадковує IReadable
public interface IWritable : IReadable
{
    void Write(string content);
}

// Клас, що реалізує IWritable, ПОВИНЕН реалізувати обидва інтерфейси
public class File : IWritable
{
    private string _content = "";

    // Від IReadable
    public string Read()
    {
        return _content;
    }

    // Від IWritable
    public void Write(string content)
    {
        _content = content;
    }
}

Множинне Успадкування Інтерфейсів

На відміну від класів, інтерфейси підтримують множинне успадкування:

public interface ILoggable
{
    void Log(string message);
}

public interface ISerializable
{
    string Serialize();
    void Deserialize(string data);
}

public interface IValidatable
{
    bool Validate();
}

// Інтерфейс успадковує від трьох інших
public interface IEntity : ILoggable, ISerializable, IValidatable
{
    int Id { get; set; }
    string Name { get; set; }
}

// Реалізація ВСІХ методів з ВСІХ інтерфейсів
public class Customer : IEntity
{
    public int Id { get; set; }
    public string Name { get; set; }

    public void Log(string message)
    {
        Console.WriteLine($"[Customer {Id}] {message}");
    }

    public string Serialize()
    {
        return $"{Id}|{Name}";
    }

    public void Deserialize(string data)
    {
        var parts = data.Split('|');
        Id = int.Parse(parts[0]);
        Name = parts[1];
    }

    public bool Validate()
    {
        return Id > 0 && !string.IsNullOrWhiteSpace(Name);
    }
}

Interface Hierarchy з Default Methods

public interface IRepository<T>
{
    void Add(T item);
    void Remove(T item);
}

public interface IQueryableRepository<T> : IRepository<T>
{
    T GetById(int id);
    IEnumerable<T> GetAll();

    // Default method, що використовує GetAll
    int Count() => GetAll().Count();
}

public interface IAdvancedRepository<T> : IQueryableRepository<T>
{
    // Default method, що використовує методи з базових інтерфейсів
    T FindOrCreate(int id)
    {
        var existing = GetById(id);
        if (existing != null)
        {
            return existing;
        }

        var newItem = Activator.CreateInstance<T>();
        Add(newItem);
        return newItem;
    }
}
Loading diagram...
graph TD
    A[IRepository&lt;T&gt;<br/>Add, Remove] --> B[IQueryableRepository&lt;T&gt;<br/>GetById, GetAll, Count]
    B --> C[IAdvancedRepository&lt;T&gt;<br/>FindOrCreate]
    C --> D[ConcreteRepository<br/>Реалізує ВСІ методи]

    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B fill:#64748b,stroke:#334155,color:#ffffff
    style C fill:#64748b,stroke:#334155,color:#ffffff
    style D fill:#f59e0b,stroke:#b45309,color:#ffffff

Member Hiding та Reimplementation

Приховування членів (member hiding) відбувається, коли похідний інтерфейс оголошує член з тим самим ім'ям:

public interface IBase
{
    void Display();
}

public interface IDerived : IBase
{
    // "Ховає" метод з IBase
    new void Display();
}

public class MyClass : IDerived
{
    // Реалізація для IDerived.Display
    public void Display()
    {
        Console.WriteLine("IDerived.Display");
    }

    // Якщо потрібна явна реалізація для IBase
    void IBase.Display()
    {
        Console.WriteLine("IBase.Display");
    }
}

// Використання
MyClass obj = new MyClass();
obj.Display(); // IDerived.Display

IBase baseRef = obj;
baseRef.Display(); // IBase.Display (якщо є explicit implementation)

IDerived derivedRef = obj;
derivedRef.Display(); // IDerived.Display

Composing Interfaces

Interface Composition — побудова складних інтерфейсів з простих:

// Атомарні інтерфейси
public interface IIdentifiable
{
    int Id { get; set; }
}

public interface ITimestamped
{
    DateTime CreatedAt { get; set; }
    DateTime? UpdatedAt { get; set; }
}

public interface IVersioned
{
    int Version { get; set; }
}

public interface IAuditable
{
    string CreatedBy { get; set; }
    string ModifiedBy { get; set; }
}

// Композитний інтерфейс
public interface IEntity : IIdentifiable, ITimestamped, IVersioned, IAuditable
{
    string Name { get; set; }
}

// Реалізація має ВСІ властивості
public class Product : IEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public int Version { get; set; }
    public string CreatedBy { get; set; }
    public string ModifiedBy { get; set; }
}
Interface Segregation Principle (ISP)Розбивайте великі інтерфейси на менші, специфічні. Це дозволяє класам реалізувати тільки ті можливості, які їм потрібні:
// ❌ Погано — монолітний інтерфейс
interface IRepository
{
    void Add(T item);
    void Remove(T item);
    T GetById(int id);
    IEnumerable<T> Search(string query);
    void BatchUpdate(List<T> items);
    void Export(string filename);
}

// ✅ Добре — сегментовані інтерфейси
interface IWriteRepository { void Add(T item); void Remove(T item); }
interface IReadRepository { T GetById(int id); }
interface ISearchRepository { IEnumerable<T> Search(string query); }
interface IBatchRepository { void BatchUpdate(List<T> items); }
interface IExportRepository { void Export(string filename); }

Static Abstract Members in Interfaces (C# 11)

Static Abstract Members (статичні абстрактні члени) — революційна можливість C# 11, яка дозволяє визначати статичні методи, властивості та оператори в інтерфейсах.

Навіщо це потрібно?

Проблема: До C# 11 неможливо було створити generic метод, який працює з операторами:

// ❌ Не працює до C# 11
public static T Add<T>(T a, T b)
{
    return a + b; // Error: Operator '+' cannot be applied to operands of type 'T'
}

Рішення з C# 11: Інтерфейси з static abstract operators

// Інтерфейс з оператором додавання
public interface IAddable<T> where T : IAddable<T>
{
    static abstract T operator +(T left, T right);
}

// Загальний метод, що працює з будь-яким типом, який підтримує додавання
public static T Add<T>(T a, T b) where T : IAddable<T>
{
    return a + b; // ✅ Компілюється!
}

Теоретичні аспекти Static Abstract Members:

Static abstract members у інтерфейсах представляють нову парадигму в мові C#. Це дозволяє визначати статичні члени (методи, оператори, властивості, події) у інтерфейсах, які мають бути реалізовані в кожному типі, що реалізує цей інтерфейс.

Особливості реалізації:

  1. Static abstract члени не можуть мати реалізацію за замовчуванням
  2. Вони не наслідуються між класами, але вимагають реалізації в кожному типі
  3. Вони дозволяють реалізувати шаблон Type Class з функціональних мов
  4. Вони дозволяють створювати generic алгоритми, які працюють з операторами та іншими статичними елементами

Це стало можливим завдяки змінам у CLR, які дозволяють реєструвати статичні віртуальні члени у системі типів.

Generic Math з .NET 7+

.NET 7 додав систему інтерфейсів для Generic Math:

using System.Numerics;

// Метод, що працює з БУДЬ-ЯКИМ числовим типом
public static T Sum<T>(T[] numbers) where T : INumber<T>
{
    T sum = T.Zero; // Static property з інтерфейсу
    foreach (T number in numbers)
    {
        sum += number; // Operator + з інтерфейсу
    }
    return sum;
}

// Використання з різними типами
int[] integers = { 1, 2, 3, 4, 5 };
Console.WriteLine(Sum(integers)); // 15

double[] doubles = { 1.5, 2.5, 3.5 };
Console.WriteLine(Sum(doubles)); // 7.5

decimal[] decimals = { 1.1m, 2.2m, 3.3m };
Console.WriteLine(Sum(decimals)); // 6.6

Створення Власного Числового Типу

using System.Numerics;

// Власний тип Vector2D, який підтримує Generic Math
public record struct Vector2D(double X, double Y)
    : IAdditionOperators<Vector2D, Vector2D, Vector2D>,
      ISubtractionOperators<Vector2D, Vector2D, Vector2D>,
      IMultiplyOperators<Vector2D, double, Vector2D>,
      IAdditiveIdentity<Vector2D, Vector2D>
{
    // Static abstract member: Operator +
    public static Vector2D operator +(Vector2D left, Vector2D right)
    {
        return new Vector2D(left.X + right.X, left.Y + right.Y);
    }

    // Static abstract member: Operator -
    public static Vector2D operator -(Vector2D left, Vector2D right)
    {
        return new Vector2D(left.X - right.X, left.Y - right.Y);
    }

    // Static abstract member: Operator * (скалярне множення)
    public static Vector2D operator *(Vector2D left, double right)
    {
        return new Vector2D(left.X * right, left.Y * right);
    }

    // Static abstract property: AdditiveIdentity (нульовий вектор)
    public static Vector2D AdditiveIdentity => new Vector2D(0, 0);
}

// Тепер можна використовувати Vector2D у generic методах!
public static T MidPoint<T>(T a, T b)
    where T : IAdditionOperators<T, T, T>,
              IMultiplyOperators<T, double, T>
{
    return (a + b) * 0.5;
}

// Використання
Vector2D v1 = new Vector2D(10, 20);
Vector2D v2 = new Vector2D(30, 40);
Vector2D middle = MidPoint(v1, v2); // (20, 30)
Console.WriteLine(middle); // Vector2D { X = 20, Y = 30 }

Інтерфейс INumber

INumber<T> — це базовий інтерфейс для всіх числових типів у .NET 7+:

public interface INumber<TSelf>
    : IAdditionOperators<TSelf, TSelf, TSelf>,
      ISubtractionOperators<TSelf, TSelf, TSelf>,
      IMultiplyOperators<TSelf, TSelf, TSelf>,
      IDivisionOperators<TSelf, TSelf, TSelf>,
      IModulusOperators<TSelf, TSelf, TSelf>,
      IComparisonOperators<TSelf, TSelf, bool>,
      IAdditiveIdentity<TSelf, TSelf>,
      IMultiplicativeIdentity<TSelf, TSelf>
    where TSelf : INumber<TSelf>
{
    static abstract TSelf One { get; }
    static abstract TSelf Zero { get; }
    static abstract TSelf Parse(string s, IFormatProvider? provider);
    static abstract bool TryParse(string? s, IFormatProvider? provider, out TSelf result);
    // ... та багато інших
}

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

// Обчислення середнього для будь-якого числового типу
public static T Average<T>(params T[] values) where T : INumber<T>
{
    if (values.Length == 0)
    {
        return T.Zero;
    }

    T sum = T.Zero;
    foreach (T value in values)
    {
        sum += value;
    }

    // Ділення на кількість елементів
    int count = values.Length;
    return sum / T.CreateChecked(count);
}

// Використання
Console.WriteLine(Average(1, 2, 3, 4, 5));           // 3 (int)
Console.WriteLine(Average(1.5, 2.5, 3.5));           // 2.5 (double)
Console.WriteLine(Average(1.1m, 2.2m, 3.3m, 4.4m));  // 2.75 (decimal)

Static Abstract Properties

public interface IParseable<TSelf> where TSelf : IParseable<TSelf>
{
    // Static abstract method
    static abstract TSelf Parse(string input);

    // Static abstract property
    static abstract string DefaultFormat { get; }
}

public record Currency(decimal Amount, string Code) : IParseable<Currency>
{
    public static Currency Parse(string input)
    {
        var parts = input.Split(' ');
        return new Currency(decimal.Parse(parts[0]), parts[1]);
    }

    public static string DefaultFormat => "0.00";
}

// Використання в generic коді
public static T ParseValue<T>(string input) where T : IParseable<T>
{
    Console.WriteLine($"Default format: {T.DefaultFormat}");
    return T.Parse(input);
}

Currency money = ParseValue<Currency>("100.50 USD"); // Currency { Amount = 100.50, Code = USD }

Type Classes Pattern

Static abstract members дозволяють реалізувати Type Classes pattern з функціональних мов (Haskell, F#):

// Type class для типів, які можна сортувати
public interface IComparable<TSelf> where TSelf : IComparable<TSelf>
{
    static abstract int Compare(TSelf left, TSelf right);
}

// Generic алгоритм сортування
public static void QuickSort<T>(T[] array) where T : IComparable<T>
{
    QuickSortInternal(array, 0, array.Length - 1);
}

private static void QuickSortInternal<T>(T[] array, int low, int high) where T : IComparable<T>
{
    if (low < high)
    {
        int pi = Partition(array, low, high);
        QuickSortInternal(array, low, pi - 1);
        QuickSortInternal(array, pi + 1, high);
    }
}

private static int Partition<T>(T[] array, int low, int high) where T : IComparable<T>
{
    T pivot = array[high];
    int i = low - 1;

    for (int j = low; j < high; j++)
    {
        if (T.Compare(array[j], pivot) < 0)
        {
            i++;
            (array[i], array[j]) = (array[j], array[i]);
        }
    }

    (array[i + 1], array[high]) = (array[high], array[i + 1]);
    return i + 1;
}

Обмеження Static Abstract Members

Важливі обмеження:
  1. Тільки в interfaces: Static abstract члени не можуть бути в класах
  2. Constraint required: Потрібен constraint where T : IInterface
  3. Немає default implementation: На відміну від instance default methods
  4. C# 11+: Підтримується тільки в C# 11 та новіших версіях
// ❌ Не працює — Static abstract у класі
public abstract class MyClass
{
    public static abstract void Method(); // Error!
}

// ✅ Працює — Static abstract у інтерфейсі
public interface IMyInterface
{
    static abstract void Method();
}

Порівняльна Таблиця Концепцій

КонцепціяC# ВерсіяОсновне ПризначенняПриклад Використання
Explicit ImplementationC# 1.0Розв'язання конфліктів іменМножинна реалізація інтерфейсів
Interface InheritanceC# 1.0Побудова ієрархійIWritable : IReadable
Default Interface MethodsC# 8.0Еволюція API без breaking changesДодавання методів у існуючі інтерфейси
Protected/Private в інтерфейсахC# 8.0Інкапсуляція логікиHelper methods всередині інтерфейсу
Static Abstract MembersC# 11.0Generic Math, операціїINumber<T>, оператори
Static Virtual MembersC# 11.0Override статичних методівFactory patterns у generic інтерфейсах
Внутрішня реалізація (vtables)CLRВіртуальні таблиці методівПідтримка default та static members
Ім'я манґлінг (name mangling)КомпіляторУнікальні імена для методівЯвна реалізація інтерфейсів

Best Practices та Design Guidelines

Коли використовувати Explicit Implementation:Використовуйте:
  • Конфлікт імен між інтерфейсами
  • Приховування utility методів (наприклад, IDisposable.Dispose)
  • Різні реалізації для різних інтерфейсів
Не використовуйте:
  • Для основного API класу
  • Коли немає конфліктів
Коли використовувати Default Interface Methods:Використовуйте:
  • Еволюція публічних API (бібліотеки, NuGet пакети)
  • Utility methods, які можна виразити через існуючі члени
  • Optional behavior для інтерфейсів
Не використовуйте:
  • Коли потрібен стан (fields)
  • Складна бізнес-логіка (краще abstract class)
Коли використовувати Static Abstract Members:Використовуйте:
  • Generic Math алгоритми
  • Operators у generic коді
  • Factory patterns
  • Type classes з функціонального програмування
Не використовуйте:
  • У старих версіях C# (< 11)
  • Коли можна обійтись instance методами

Еволюція інтерфейсів у C#

Loading diagram...
graph TB
    A[C# 1.0: Basic Interfaces] --> B[C# 8.0: Default Interface Methods]
    B --> C[C# 11.0: Static Abstract Members]

    A1[Abstract Classes] --> B1[Interface Inheritance]
    B1 --> C1[Static Virtual Members]

    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B fill:#64748b,stroke:#334155,color:#ffffff
    style C fill:#f59e0b,stroke:#b45309,color:#ffffff
    style A1 fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B1 fill:#64748b,stroke:#334155,color:#ffffff
    style C1 fill:#f59e0b,stroke:#b45309,color:#ffffff

Принципи дизайну інтерфейсів:

  1. Interface Segregation Principle (ISP): Клієнти не повинні залежати від інтерфейсів, які вони не використовують. Краще мати кілька спеціалізованих інтерфейсів, ніж один великий.
  2. Composition over Inheritance: Використовуйте успадкування інтерфейсів для композиції функціональності, а не для моделювання ієрархії.
  3. Evolution-friendly Design: При проектуванні інтерфейсів враховуйте, що вони можуть розвиватись з використанням default methods.
  4. Type Safety: Використовуйте static abstract members для забезпечення типобезпеки в generic контекстах.

Anti-Patterns

Поширені помилки:Fat Interfaces — інтерфейси з великою кількістю методів
// Погано
interface IAnimal
{
    void Eat();
    void Sleep();
    void Fly();    // Не всі тварини літають!
    void Swim();   // Не всі тварини плавають!
    void Walk();
}

// Добре — Interface Segregation
interface IAnimal
{
    void Eat();
    void Sleep();
}

interface IFlyable
{
    void Fly();
}

interface ISwimmable
{
    void Swim();
}
Implementation Leakage — витік деталей реалізації
// Погано
interface IRepository
{
    DbContext GetDbContext(); // Витік деталей!
}

// Добре
interface IRepository
{
    void Save(Entity entity);
    Entity Get(int id);
}

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

Завдання 1: Explicit Interface Implementation (Beginner)

Мета: Навчитись розв'язувати конфлікти імен за допомогою явної реалізації.

Умова:

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

interface INetworkDevice
{
    string GetStatus();
    void Connect();
}

interface IPowerDevice
{
    string GetStatus();
    void TurnOn();
}

Клас повинен:

  • Реалізувати INetworkDevice.GetStatus() — повертає статус мережі ("Connected" або "Disconnected")
  • Реалізувати IPowerDevice.GetStatus() — повертає статус живлення ("On" або "Off")
  • Методи Connect() та TurnOn() змінюють відповідні стани

Завдання 2: Default Interface Methods і Diamond Problem (Intermediate)

Мета: Зрозуміти default interface methods та навчитись розв'язувати Diamond Problem.

Умова:

Створіть ієрархію інтерфейсів для системи логування:

interface ILogger
{
    void Log(string message);
}

interface ITimestampedLogger : ILogger
{
    // Default method — додає timestamp
    void ILogger.Log(string message)
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {message}");
    }
}

interface IColorLogger : ILogger
{
    // Default method — додає колір (символічно через префікс)
    void ILogger.Log(string message)
    {
        Console.WriteLine($"[COLOR] {message}");
    }
}

Створіть клас AdvancedLogger, який реалізує обидва інтерфейси та вирішує Diamond Problem, комбінуючи обидві функції (timestamp + color).


Завдання 3: Generic Math з Static Abstract Members (Advanced)

Мета: Створити generic алгоритм з використанням Static Abstract Members.

Умова:

Реалізуйте generic метод обчислення середньоквадратичного відхилення (standard deviation) для будь-якого числового типу.

Формула: $\sigma = \sqrt{\frac{1}{N}\sum_^{N}(x_i - \bar{x})^2}$

Використовуйте інтерфейс INumber<T> та створіть метод:

public static T StandardDeviation<T>(T[] values) where T : INumber<T>
{
    // Ваша реалізація
}

Резюме

У цьому розділі ми детально розглянули просунуті концепції інтерфейсів у C#:

  • Explicit Interface Implementation — розв'язання конфліктів імен та приховування деталей реалізації
  • Default Interface Methods (C# 8) — еволюція API без breaking changes та розв'язання Diamond Problem
  • Interface Inheritance — побудова складних ієрархій через множинне успадкування
  • Static Abstract Members (C# 11) — революційна можливість для Generic Math та type classes pattern

Ключові поняття:

  • Явна vs неявна реалізація інтерфейсів
  • Default implementations та їх обмеження
  • Diamond Problem та способи його розв'язання
  • Generic Math з INumber<T> та подібними інтерфейсами
  • Type Classes pattern у C#

Еволюція інтерфейсів у C#:

  1. C# 1.0: Базові інтерфейси - тільки сигнатури методів та властивостей
  2. C# 8.0: Default Interface Methods - методи з реалізацією за замовчуванням, доступні модифікатори доступу
  3. C# 11.0: Static Abstract Members - статичні абстрактні методи, властивості та оператори в інтерфейсах

Принципи дизайну інтерфейсів:

  • Interface Segregation Principle: Краще мати кілька спеціалізованих інтерфейсів, ніж один великий
  • Composition over Inheritance: Використовуйте композицію інтерфейсів для досягнення функціональності
  • Evolution-friendly Design: Проектуйте інтерфейси з урахуванням їхнього майбутнього розвитку
  • Type Safety: Використовуйте static abstract members для забезпечення типобезпеки в generic контекстах

Наступні кроки: