Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

Interfejsy przez lata były kontraktami bez implementacji - tylko deklaracje metod, nic więcej. W 2026 roku interfejsy to potężne narzędzie: mogą mieć implementację (C# 8), static abstract members (C# 11), i extension members (C# 14)!

W tym wpisie zobaczysz jak static abstract members rewolucjonizują generics, jak działa generic math, i najnowszy ficzer C# 14 - extension members dla interfejsów!

📅 Timeline - ewolucja interfejsów w C#
  • C# 1.0 (2002) - Interfejsy jako kontrakty (tylko deklaracje)
  • C# 8 (2019) - 🔥 Default interface implementations
  • C# 11 (2022) - 🔥 Static abstract members - game changer!
  • C# 11 (2022) - Generic math z INumber<T>
  • C# 14 (2026) - 🔥 Extension members dla interfejsów
Default implementations - przypomnienie (C# 8)

Interfejsy z implementacją

// C# 8 - default interface implementations (poznane w poprzednim wpisie)
public interface ILogger
{
    void Log(string message);
    
    // Default implementation - nie breaking change!
    void LogError(string error)
    {
        Log($"[ERROR] {error}");
    }
    
    void LogWarning(string warning)
    {
        Log($"[WARNING] {warning}");
    }
}

// Klasy implementują tylko Log, reszta z default
public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine(message);
}

ILogger logger = new ConsoleLogger();
logger.LogError("Something went wrong");  // [ERROR] Something went wrong

Properties i private methods w interfejsach

// C# 8 - interfejsy mogą mieć więcej!
public interface IRepository
{
    // Property z default value
    string ConnectionString => "Server=localhost";
    
    // Private helper method!
    private string GetTableName(Type type)
    {
        return type.Name + "s";
    }
    
    // Public method używa private helper
    void Save(T entity)
    {
        var tableName = GetTableName(typeof(T));
        Console.WriteLine($"Saving to table: {tableName}");
    }
}
🔥 Static Abstract Members - rewolucja! (C# 11)

Problem przed C# 11 - brak static w interfejsach

// Chcesz stworzyć generyczną metodę Parse
public static T Parse(string input)
{
    // ❌ Jak wywołać T.Parse(input)?
    // Nie ma sposobu wymusić że T ma static Parse!
    
    // Musisz użyć reflection (wolne!) lub switch
    if (typeof(T) == typeof(int))
        return (T)(object)int.Parse(input);
    if (typeof(T) == typeof(double))
        return (T)(object)double.Parse(input);
    // ... dla każdego typu osobno 😱
    
    throw new NotSupportedException();
}

// Problem: nie możesz wymusić static members w generic constraints!
// where T : ??? NIE MA SPOSOBU dla static!

Rozwiązanie - Static Abstract Members!

🎉 C# 11 - Static Abstract Members

Interfejsy mogą definiować static abstract metody/properties/operatory! Generic constraints mogą wymagać static members!

// C# 11 - interfejs ze static abstract members
public interface IParsable where TSelf : IParsable
{
    // Static abstract method - MUSI być zaimplementowana jako static!
    static abstract TSelf Parse(string input);
    
    // Static abstract property
    static abstract string TypeName { get; }
}

// Implementacja - static members!
public class Temperature : IParsable
{
    public double Celsius { get; init; }
    
    // Implementacja static abstract method
    public static Temperature Parse(string input)
    {
        var value = double.Parse(input);
        return new Temperature { Celsius = value };
    }
    
    // Implementacja static abstract property
    public static string TypeName => "Temperature";
}

// Teraz możesz użyć static members w generic constraints!
public static T ParseGeneric(string input) where T : IParsable
{
    return T.Parse(input);  // ✅ Wywołanie static method!
}

// Użycie
var temp = ParseGeneric("25.5");
Console.WriteLine(temp.Celsius);  // 25.5

// To działa bo Temperature implementuje IParsable!
❌ Przed C# 11 - reflection
public static T Parse(string input)
{
    // Reflection - wolne i unsafe!
    var method = typeof(T).GetMethod("Parse", 
        new[] { typeof(string) });
    
    if (method == null)
        throw new InvalidOperationException();
    
    return (T)method.Invoke(null, new[] { input });
}

// Brak type safety, wolne, może crashować
✅ C# 11 - static abstract
public static T Parse(string input) 
    where T : IParsable
{
    return T.Parse(input);  // ✅ Type safe!
}

// Kompilator sprawdza w compile-time!
// Szybkie, bezpieczne! ✨

Static abstract operators

// C# 11 - interfejs ze static operators!
public interface IAddable where TSelf : IAddable
{
    // Static abstract operator
    static abstract TSelf operator +(TSelf left, TSelf right);
    
    // Static abstract Zero
    static abstract TSelf Zero { get; }
}

public class Vector : IAddable
{
    public double X { get; init; }
    public double Y { get; init; }
    
    // Implementacja operator
    public static Vector operator +(Vector left, Vector right)
    {
        return new Vector 
        { 
            X = left.X + right.X, 
            Y = left.Y + right.Y 
        };
    }
    
    // Implementacja Zero
    public static Vector Zero => new Vector { X = 0, Y = 0 };
}

// Generic sum używa operator+!
public static T Sum(T[] values) where T : IAddable
{
    T result = T.Zero;  // Użycie static property
    
    foreach (var value in values)
    {
        result = result + value;  // Użycie operator+
    }
    
    return result;
}

// Użycie
var vectors = new[]
{
    new Vector { X = 1, Y = 2 },
    new Vector { X = 3, Y = 4 },
    new Vector { X = 5, Y = 6 }
};

var total = Sum(vectors);
Console.WriteLine($"Total: ({total.X}, {total.Y})");  // (9, 12)
Generic Math - praktyczne zastosowanie (C# 11)

Problem przed C# 11 - brak generic math

// Przed C# 11 - musisz pisać osobno dla każdego typu!

public int Sum(int[] numbers)
{
    int total = 0;
    foreach (var num in numbers)
        total += num;
    return total;
}

public double Sum(double[] numbers)
{
    double total = 0;
    foreach (var num in numbers)
        total += num;
    return total;
}

public decimal Sum(decimal[] numbers)
{
    decimal total = 0;
    foreach (var num in numbers)
        total += num;
    return total;
}

// Code duplication! 😱
// Nie możesz napisać jednej generic metody dla wszystkich typów numerycznych!

🔥 INumber<T> - generic math!

🎉 C# 11 - Generic Math z INumber<T>

.NET 7+ ma interfejsy dla operacji numerycznych: INumber<T>, IAdditionOperators<T>, etc.

// C# 11 - generic math!
using System.Numerics;

public static T Sum(T[] numbers) where T : INumber
{
    T total = T.Zero;  // Static property z interfejsu!
    
    foreach (var num in numbers)
    {
        total += num;  // operator+ z interfejsu!
    }
    
    return total;
}

// Działa dla WSZYSTKICH typów numerycznych!
var intSum = Sum(new[] { 1, 2, 3, 4, 5 });           // int: 15
var doubleSum = Sum(new[] { 1.5, 2.5, 3.5 });       // double: 7.5
var decimalSum = Sum(new[] { 1.1m, 2.2m, 3.3m });   // decimal: 6.6

// Jedna metoda dla wszystkich! ✨

INumber<T> - dostępne operacje

// INumber zapewnia:
// - Operatory: +, -, *, /, %, ==, !=, <,>, <=, >=
// - Static properties: Zero, One, AdditiveIdentity, MultiplicativeIdentity
// - Metody: Abs, Sign, Max, Min, Clamp
// - Parse/TryParse

public static T Average(T[] numbers) where T : INumber
{
    if (numbers.Length == 0)
        return T.Zero;
    
    T sum = Sum(numbers);
    T count = T.CreateChecked(numbers.Length);  // Convert int to T
    return sum / count;
}

public static T Max(T[] numbers) where T : INumber
{
    if (numbers.Length == 0)
        throw new ArgumentException("Empty array");
    
    T max = numbers[0];
    
    foreach (var num in numbers)
    {
        if (num > max)  // operator>
            max = num;
    }
    
    return max;
}

// Użycie
var intAvg = Average(new[] { 1, 2, 3, 4, 5 });       // 3
var doubleMax = Max(new[] { 1.5, 9.2, 3.7 });       // 9.2

Więcej numeric interfejsów

using System.Numerics;

// IAdditionOperators - tylko operator+
public static T Add(T a, T b) where T : IAdditionOperators
{
    return a + b;
}

// IComparisonOperators - operatory porównania
public static T Min(T a, T b) where T : IComparisonOperators
{
    return a < b ? a : b;
}

// IFloatingPoint - tylko float/double
public static T Sqrt(T value) where T : IFloatingPoint
{
    return T.Sqrt(value);  // Static abstract method
}

// ISignedNumber - liczby ze znakiem (nie uint!)
public static T Negate(T value) where T : ISignedNumber
{
    return -value;
}

// Użycie
var sum = Add(5, 10);              // int: 15
var min = Min(3.5, 2.1);          // double: 2.1
var sqrt = Sqrt(16.0);            // double: 4.0
var neg = Negate(-5);             // int: 5
💡 Praktyczne przykłady - generic math
// Przykład 1: Generic statistics
public static (T min, T max, T avg) GetStats(T[] numbers) 
    where T : INumber
{
    if (numbers.Length == 0)
        throw new ArgumentException("Empty array");
    
    T min = numbers[0];
    T max = numbers[0];
    T sum = T.Zero;
    
    foreach (var num in numbers)
    {
        if (num < min) min = num;
        if (num > max) max = num;
        sum += num;
    }
    
    T count = T.CreateChecked(numbers.Length);
    T avg = sum / count;
    
    return (min, max, avg);
}

var stats = GetStats(new[] { 1.5, 9.2, 3.7, 2.1, 7.8 });
Console.WriteLine($"Min: {stats.min}, Max: {stats.max}, Avg: {stats.avg}");
// Min: 1.5, Max: 9.2, Avg: 4.86

// Przykład 2: Generic matrix
public class Matrix where T : INumber
{
    private readonly T[,] _data;
    
    public T this[int row, int col]
    {
        get => _data[row, col];
        set => _data[row, col] = value;
    }
    
    public static Matrix operator +(Matrix a, Matrix b)
    {
        // ... dodawanie macierzy używając T operator+
    }
    
    public static Matrix operator *(Matrix a, Matrix b)
    {
        // ... mnożenie macierzy używając T operator* i +
    }
}

var m1 = new Matrix(2, 2);
var m2 = new Matrix(2, 2);
var result = m1 + m2;  // Działa dla double, int, decimal, etc!
🔥 Extension Members dla interfejsów (C# 14)

Problem - extension methods dla interfejsów

// Extension methods dla interfejsów działały zawsze
public interface ILogger
{
    void Log(string message);
}

// Extension method
public static class LoggerExtensions
{
    public static void LogError(this ILogger logger, string error)
    {
        logger.Log($"[ERROR] {error}");
    }
}

// Użycie
ILogger logger = new ConsoleLogger();
logger.LogError("Error!");  // Działa przez extension method

// ⚠️ ALE: extension methods NIE są częścią interfejsu!
// Nie możesz ich nadpisać w implementacji
// Nie pojawiają się w IntelliSense gdy patrzysz na interfejs

🔥 C# 14 - Extension Members!

🎉 NOWOŚĆ C# 14 - Extension Members dla interfejsów

extension interface - rozszerzaj interfejsy z properties i methods!

// C# 14 - extension interface
public interface ILogger
{
    void Log(string message);
}

// Extension members dla ILogger
public extension interface ILoggerExtensions for ILogger
{
    // Extension property
    string Prefix => "[LOG]";
    
    // Extension method
    void LogError(string error)
    {
        Log($"{Prefix} [ERROR] {error}");
    }
    
    void LogWarning(string warning)
    {
        Log($"{Prefix} [WARNING] {warning}");
    }
    
    void LogInfo(string info)
    {
        Log($"{Prefix} [INFO] {info}");
    }
}

// Implementacja - automatycznie ma extension members!
public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine(message);
    
    // LogError, LogWarning, LogInfo są dostępne automatycznie!
}

// Użycie
ILogger logger = new ConsoleLogger();
logger.LogError("Error!");      // [LOG] [ERROR] Error!
logger.LogWarning("Warning!");  // [LOG] [WARNING] Warning!
logger.LogInfo("Info!");        // [LOG] [INFO] Info!

// Extension members SĄ częścią interfejsu! ✨

Override extension members

// C# 14 - możesz nadpisać extension members!
public extension interface IRepositoryExtensions for IRepository
{
    // Extension method
    async Task FindFirstAsync(Func predicate)
    {
        var all = await GetAllAsync();
        return all.FirstOrDefault(predicate);
    }
    
    // Extension property
    string RepositoryName => $"{typeof(T).Name}Repository";
}

// Implementacja może nadpisać!
public class UserRepository : IRepository
{
    public async Task GetByIdAsync(int id) { /* ... */ }
    public async Task> GetAllAsync() { /* ... */ }

    // Override extension method - własna optymalizacja!
    public async Task FindFirstAsync(Func predicate)
    {
        // Zoptymalizowane query zamiast GetAllAsync + LINQ
        return await Database.Users.FirstOrDefaultAsync(predicate);
    }

    // Override extension property
    public string RepositoryName => "OptimizedUserRepository";
}

Extension members vs Default implementations

Feature Default Implementation (C# 8) Extension Members (C# 14)
Gdzie definiowane W samym interfejsie W osobnym extension interface
Dostęp przez class ref ❌ Tylko przez interface ref ✅ Przez class i interface ref
Widoczność w IntelliSense Tylko na interface Na interface i class
Można nadpisać? ✅ Tak ✅ Tak
Breaking change? ✅ Nie (default impl) ✅ Nie (extension)
Kiedy używać Metody core do interfejsu Helper methods, convenience
// Default implementation (C# 8)
public interface ILogger
{
    void Log(string message);
    
    // Default - część interfejsu
    void LogError(string error) => Log($"ERROR: {error}");
}

ILogger logger = new ConsoleLogger();
logger.LogError("Error");  // ✅ OK

ConsoleLogger directLogger = new ConsoleLogger();
// directLogger.LogError("Error");  // ❌ BŁĄD - nie ma w ConsoleLogger!

// Extension member (C# 14)
public extension interface ILoggerExtensions for ILogger
{
    void LogWarning(string warning) => Log($"WARNING: {warning}");
}

ILogger logger2 = new ConsoleLogger();
logger2.LogWarning("Warning");  // ✅ OK

ConsoleLogger directLogger2 = new ConsoleLogger();
directLogger2.LogWarning("Warning");  // ✅ OK! Dostępne przez class ref! ✨
Praktyczne przykłady - interfejsy C# 14

Przykład 1: Generic repository z extension members

// Base interface
public interface IRepository
{
    Task GetByIdAsync(int id);
    Task> GetAllAsync();
    Task SaveAsync(T entity);
    Task DeleteAsync(int id);
}

// Extension members (C# 14)
public extension interface IRepositoryExtensions for IRepository
{
    // Query helpers
    async Task FindFirstAsync(Func predicate)
    {
        var all = await GetAllAsync();
        return all.FirstOrDefault(predicate);
    }

    async Task> FindManyAsync(Func predicate)
    {
        var all = await GetAllAsync();
        return all.Where(predicate);
    }

    async Task CountAsync()
    {
        var all = await GetAllAsync();
        return all.Count();
    }

    // Batch operations
    async Task SaveManyAsync(IEnumerable entities)
    {
        foreach (var entity in entities)
        {
            await SaveAsync(entity);
        }
    }
}

// Implementacja - automatycznie ma wszystkie extension members!
public class UserRepository : IRepository
{
    public async Task GetByIdAsync(int id) { /* ... */ }
    public async Task> GetAllAsync() { /* ... */ }
    public async Task SaveAsync(User entity) { /* ... */ }
    public async Task DeleteAsync(int id) { /* ... */ }

    // Wszystkie extension methods dostępne bez implementacji! ✨
}

// Użycie
var repo = new UserRepository();

// Core methods
var user = await repo.GetByIdAsync(123);

// Extension methods - dostępne automatycznie!
var admin = await repo.FindFirstAsync(u => u.Role == "Admin");
var count = await repo.CountAsync();

Przykład 2: INumber<T> z extension members

using System.Numerics;

// Extension dla INumber
public extension interface INumberExtensions for INumber 
    where T : INumber
{
    // Extension: squared
    T Squared() => (T)this * (T)this;
    
    // Extension: cubed
    T Cubed() => (T)this * (T)this * (T)this;
    
    // Extension: is even
    bool IsEven() => (T)this % T.CreateChecked(2) == T.Zero;
    
    // Extension: is positive
    bool IsPositive() => (T)this > T.Zero;
    
    // Extension: clamp
    T Clamp(T min, T max)
    {
        if ((T)this < min) return min;
        if ((T)this > max) return max;
        return (T)this;
    }
}

// Użycie - extension members dostępne na WSZYSTKICH numeric types!
int x = 5;
Console.WriteLine(x.Squared());    // 25 (extension!)
Console.WriteLine(x.IsEven());     // false
Console.WriteLine(x.IsPositive()); // true

double y = 2.5;
Console.WriteLine(y.Cubed());      // 15.625
Console.WriteLine(y.Clamp(0, 2));  // 2.0

// Działa dla int, double, decimal, float, etc! ✨

Przykład 3: IDisposable z safe disposal

// Extension dla IDisposable
public extension interface IDisposableExtensions for IDisposable
{
    // Safe disposal - nie rzuca exceptions
    void SafeDispose()
    {
        try
        {
            Dispose();
        }
        catch (Exception ex)
        {
            // Log error ale nie propaguj
            Console.WriteLine($"Disposal error: {ex.Message}");
        }
    }
    
    // Dispose po delay
    async Task DisposeAfterAsync(TimeSpan delay)
    {
        await Task.Delay(delay);
        Dispose();
    }
}

// Użycie - dostępne na WSZYSTKICH IDisposable!
using var file = File.OpenRead("data.txt");
file.SafeDispose();  // Nie rzuci exception jeśli coś pójdzie nie tak

var connection = new SqlConnection(connectionString);
await connection.DisposeAfterAsync(TimeSpan.FromSeconds(5));
Podsumowanie

Interfejsy w nowej odsłonie - od kontraktów do potężnych narzędzi!

  • Default implementations (C# 8) - interfejsy z kodem
  • 🔥 Static abstract members (C# 11) - static w generic constraints!
  • Generic math (C# 11) - INumber<T>, jedna metoda dla wszystkich typów
  • Numeric interfaces - IAdditionOperators, IComparisonOperators, IFloatingPoint
  • 🔥🔥 Extension members (C# 14) - rozszerzaj interfejsy elegancko!
  • Extension vs Default - różnice i kiedy czego używać
  • Praktyczne przykłady - repository pattern, numeric extensions, IDisposable helpers

W kolejnym wpisie poznasz typy generyczne - variance (in/out), constraints, generic constraints z where!

Zadanie dla Ciebie 🎯

Stwórz generic calculator używając INumber<T> i extension members:

// Interface
public interface ICalculator where T : INumber
{
    T Add(T a, T b);
    T Subtract(T a, T b);
    T Multiply(T a, T b);
    T Divide(T a, T b);
}

// Extension members do stworzenia:
// 1. Power(T value, int exponent)
// 2. Average(params T[] values)
// 3. Factorial(T n) - tylko dla integer types
// 4. Percentage(T value, T percent)

Wymagania:

  1. Implementuj Calculator<T> dla podstawowych operacji
  2. Stwórz extension interface ICalculatorExtensions<T>
  3. Dodaj extension members wymienione wyżej
  4. Testuj z int, double, decimal
🎯 BONUS: Generic Vector Math Library

Stwórz kompletną bibliotekę matematyki wektorowej używając C# 11/14!

Do zaimplementowania:

  1. IVector<T> interface ze static abstract members:
    • static abstract IVector<T> Zero
    • static abstract IVector<T> operator +(IVector<T>, IVector<T>)
    • static abstract IVector<T> operator -(IVector<T>, IVector<T>)
    • static abstract IVector<T> operator *(IVector<T>, T scalar)
    • T Magnitude { get; }
  2. Extension members (C# 14):
    • IVector<T> Normalize()
    • T DotProduct(IVector<T> other)
    • T Distance(IVector<T> other)
    • IVector<T> Lerp(IVector<T> target, T t)
  3. Implementacje:
    • Vector2<T> - 2D vector
    • Vector3<T> - 3D vector
  4. Generic constraints:
    • where T : INumber<T>, IFloatingPoint<T>

Przykład użycia:

// Działa z double
var v1 = new Vector2(3, 4);
var v2 = new Vector2(1, 2);

var sum = v1 + v2;              // Operator
var scaled = v1 * 2;            // Scalar multiply
var magnitude = v1.Magnitude;   // 5.0
var normalized = v1.Normalize(); // Extension member
var dot = v1.DotProduct(v2);    // Extension member
var distance = v1.Distance(v2); // Extension member

// Działa też z float
var v3 = new Vector3(1f, 2f, 3f);
var v4 = new Vector3(4f, 5f, 6f);
var lerp = v3.Lerp(v4, 0.5f);   // Extension member

Console.WriteLine($"Lerp: ({lerp.X}, {lerp.Y}, {lerp.Z})");

Ten projekt demonstruje pełną moc C# 11/14 - static abstract members, generic math, extension members! 🚀✨🔢