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
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:
Implementuj Calculator<T> dla podstawowych operacji
static abstract IVector<T> operator *(IVector<T>, T scalar)
T Magnitude { get; }
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)
Implementacje:
Vector2<T> - 2D vector
Vector3<T> - 3D vector
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! 🚀✨🔢