Extension methods (C# 3.0, 2007) były rewolucją - pozwoliły rozszerzać typy bez modyfikowania ich kodu. Ale przez 19 lat mogłeś rozszerzać tylko metody. W 2026 roku wszystko się zmienia!
C# 14 wprowadza kompletny system extension members: extension properties, extension operators, static extension members, i nową składnię extension blocks. To największa zmiana w rozszerzalności typów od czasu LINQ!
// Extension methods - znane od C# 3.0 (2007)
public static class StringExtensions
{
// Extension method - static method w static class
// Pierwszy parametr z 'this' wskazuje rozszerzany typ
public static string Reverse(this string str)
{
char[] chars = str.ToCharArray();
Array.Reverse(chars);
return new string(chars);
}
public static bool IsNullOrEmpty(this string? str)
{
return string.IsNullOrEmpty(str);
}
public static int WordCount(this string str)
{
return str.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}
// Użycie - jak normalne metody!
string text = "Hello World";
string reversed = text.Reverse(); // "dlroW olleH"
int words = text.WordCount(); // 2
bool empty = text.IsNullOrEmpty(); // false
// LINQ to extension methods!
var numbers = new[] { 1, 2, 3, 4, 5 };
var even = numbers.Where(n => n % 2 == 0); // Where to extension method!
Ograniczenia extension methods
// Do C# 13 - TYLKO methods!
public static class StringExtensions
{
// ✅ Extension method - OK
public static string Reverse(this string str) { /* ... */ }
// ❌ Extension property - NIEMOŻLIWE przed C# 14!
// public static int Length(this string str) { get; }
// ❌ Extension operator - NIEMOŻLIWE przed C# 14!
// public static string operator +(this string a, string b) { /* ... */ }
// ❌ Extension static member - NIEMOŻLIWE przed C# 14!
// public static string Empty(this string) { /* ... */ }
}
// Problemy:
// 1. Nie możesz dodać computed property
// 2. Nie możesz dodać operator overloading
// 3. Nie możesz dodać static members
// 4. Musisz tworzyć osobne static classes dla każdego typu
🔥 Extension Properties - wreszcie! (C# 14)
Problem bez extension properties
// Przed C# 14 - musisz użyć method zamiast property
public static class StringExtensions
{
// Method zamiast property 😞
public static bool IsEmpty(this string str)
{
return str.Length == 0;
}
public static string GetReversed(this string str)
{
return new string(str.Reverse().ToArray());
}
}
// Użycie - wywołanie metody, nie property access
string text = "Hello";
bool empty = text.IsEmpty(); // Wygląda jak metoda
string rev = text.GetReversed(); // Wygląda jak metoda
// Chciałbyś:
// bool empty = text.IsEmpty; // Jako property!
// string rev = text.Reversed; // Jako property!
Rozwiązanie - Extension Properties!
🎉 REWOLUCJA C# 14 - Extension Properties
Możesz rozszerzać typy o properties! Wygląda jak normalna property!
// C# 14 - extension properties!
public static class StringExtensions
{
// Extension property - get-only
public static bool IsEmpty(this string str)
{
get => str.Length == 0;
}
// Extension property - computed
public static string Reversed(this string str)
{
get => new string(str.Reverse().ToArray());
}
// Extension property - z logiką
public static int WordCount(this string str)
{
get => str.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}
// Użycie - jak normalna property! ✨
string text = "Hello World";
bool empty = text.IsEmpty; // Property access!
string rev = text.Reversed; // Property access!
int words = text.WordCount; // Property access!
// Wygląda naturalnie, jak gdyby string miał te properties! 🎉
❌ C# 3-13 - method
public static class ListExtensions
{
public static bool IsEmpty(this List list)
{
return list.Count == 0;
}
public static T GetLast(this List list)
{
return list[list.Count - 1];
}
}
// Użycie - wywołanie metody
var list = new List { 1, 2, 3 };
bool empty = list.IsEmpty(); // ()
int last = list.GetLast(); // ()
✅ C# 14 - property
public static class ListExtensions
{
public static bool IsEmpty(this List list)
{
get => list.Count == 0;
}
public static T Last(this List list)
{
get => list[list.Count - 1];
}
}
// Użycie - property access! ✨
var list = new List { 1, 2, 3 };
bool empty = list.IsEmpty; // Bez ()!
int last = list.Last; // Bez ()!
Extension properties z get i set
// Extension property z get/set
public static class ListExtensions
{
// Read-only extension property
public static bool IsEmpty(this List list)
{
get => list.Count == 0;
}
// Extension property z get/set
public static T FirstOrDefault(this List list)
{
get => list.Count > 0 ? list[0] : default(T);
set
{
if (list.Count == 0)
list.Add(value);
else
list[0] = value;
}
}
}
// Użycie
var numbers = new List { 1, 2, 3 };
// Get
int first = numbers.FirstOrDefault; // 1
// Set
numbers.FirstOrDefault = 99; // Zmienia list[0] na 99
Console.WriteLine(numbers[0]); // 99
Extension properties dla value types
// Extension properties dla DateTime
public static class DateTimeExtensions
{
public static bool IsWeekend(this DateTime date)
{
get => date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
public static bool IsWeekday(this DateTime date)
{
get => !date.IsWeekend;
}
public static int DayOfYear(this DateTime date)
{
get => date.DayOfYear;
}
public static string MonthName(this DateTime date)
{
get => date.ToString("MMMM");
}
}
// Użycie
var today = DateTime.Now;
bool weekend = today.IsWeekend; // Property!
bool weekday = today.IsWeekday; // Property!
string month = today.MonthName; // "February"
// Extension properties dla int
public static class IntExtensions
{
public static bool IsEven(this int number)
{
get => number % 2 == 0;
}
public static bool IsPositive(this int number)
{
get => number > 0;
}
public static string Ordinal(this int number)
{
get => number switch
{
1 => "1st",
2 => "2nd",
3 => "3rd",
_ => $"{number}th"
};
}
}
int x = 5;
bool even = x.IsEven; // false
bool positive = x.IsPositive; // true
string ord = x.Ordinal; // "5th"
🔥 Extension Operators (C# 14)
Problem - brak operator extensions
// Chcesz dodać operator+ dla string concatenation z obiektem
public class Person
{
public string Name { get; set; }
}
// Przed C# 14 - musisz modyfikować class Person
// public class Person
// {
// public static string operator +(string s, Person p) { /* ... */ }
// }
// Ale nie możesz modyfikować string!
// Extension method to workaround
public static class StringPersonExtensions
{
public static string Concat(this string s, Person p)
{
return s + p.Name;
}
}
// Użycie - brzydkie
string result = "Hello ".Concat(new Person { Name = "Jan" });
// Chciałbyś: "Hello " + person
Rozwiązanie - Extension Operators!
🎉 REWOLUCJA C# 14 - Extension Operators
Możesz dodawać operator overloading jako extensions!
// C# 14 - extension operator!
public static class StringPersonExtensions
{
// Extension operator+ dla string i Person
public static string operator +(this string s, Person p)
{
return s + p.Name;
}
// Extension operator* - repeat string
public static string operator *(this string s, int count)
{
return string.Concat(Enumerable.Repeat(s, count));
}
}
// Użycie - naturalny operator syntax! ✨
var person = new Person { Name = "Jan" };
string result = "Hello " + person; // "Hello Jan"
string repeated = "Ha" * 3; // "HaHaHa"
// Wygląda jak native operator! 🎉
Extension comparison operators
// Extension operators dla custom comparisons
public class Version
{
public int Major { get; set; }
public int Minor { get; set; }
}
public static class VersionExtensions
{
// Extension operator>
public static bool operator >(this Version v1, Version v2)
{
if (v1.Major != v2.Major)
return v1.Major > v2.Major;
return v1.Minor > v2.Minor;
}
// Extension operator<
public static bool operator <(this Version v1, Version v2)
{
if (v1.Major !=v2.Major)
return v1.Major < v2.Major;
return v1.Minor < v2.Minor;
}
// Extension operator==
public static bool operator ==(this Version v1, Version v2)
{
return v1.Major == v2.Major && v1.Minor == v2.Minor;
}
// Extension operator!=
public static bool operator !=(this Version v1, Version v2)
{
return !(v1 == v2);
}
}
// Użycie
var v1 = new Version { Major = 2, Minor = 1 };
var v2 = new Version { Major = 1, Minor = 9 };
bool greater = v1 > v2; // true - operator extension!
bool equal = v1 == v2; // false
Extension arithmetic operators
// Extension operators dla Vector
public class Vector2
{
public double X { get; set; }
public double Y { get; set; }
}
public static class Vector2Extensions
{
// Extension operator+
public static Vector2 operator +(this Vector2 v1, Vector2 v2)
{
return new Vector2 { X = v1.X + v2.X, Y = v1.Y + v2.Y };
}
// Extension operator-
public static Vector2 operator -(this Vector2 v1, Vector2 v2)
{
return new Vector2 { X = v1.X - v2.X, Y = v1.Y - v2.Y };
}
// Extension operator* (scalar multiply)
public static Vector2 operator *(this Vector2 v, double scalar)
{
return new Vector2 { X = v.X * scalar, Y = v.Y * scalar };
}
// Extension operator/ (scalar divide)
public static Vector2 operator /(this Vector2 v, double scalar)
{
return new Vector2 { X = v.X / scalar, Y = v.Y / scalar };
}
}
// Użycie - naturalny syntax!
var v1 = new Vector2 { X = 3, Y = 4 };
var v2 = new Vector2 { X = 1, Y = 2 };
var sum = v1 + v2; // Vector2 { X = 4, Y = 6 }
var diff = v1 - v2; // Vector2 { X = 2, Y = 2 }
var scaled = v1 * 2; // Vector2 { X = 6, Y = 8 }
var divided = v1 / 2; // Vector2 { X = 1.5, Y = 2 }
🔥 Static Extension Members (C# 14)
Problem - brak static extensions
// Chcesz dodać static factory method do typu
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
// Przed C# 14 - musisz użyć osobnej static class
public static class UserFactory
{
public static User CreateGuest()
{
return new User { Name = "Guest", Email = "guest@example.com" };
}
}
// Użycie - nie wygląda jak część User
var guest = UserFactory.CreateGuest(); // Osobna klasa
// Chciałbyś:
// var guest = User.CreateGuest(); // Jak gdyby było w User!
Rozwiązanie - Static Extension Members!
🎉 REWOLUCJA C# 14 - Static Extension Members
Możesz dodawać static members do typów przez extensions!
// C# 14 - static extension members!
public static class UserExtensions
{
// Static extension method
public static User CreateGuest(this User)
{
return new User { Name = "Guest", Email = "guest@example.com" };
}
// Static extension method z parametrami
public static User Create(this User, string name, string email)
{
return new User { Name = name, Email = email };
}
// Static extension property
public static User Empty(this User)
{
get => new User { Name = "", Email = "" };
}
}
// Użycie - wygląda jak static members User! ✨
var guest = User.CreateGuest(); // Jak gdyby było w User!
var user = User.Create("Jan", "jan@example.com");
var empty = User.Empty;
// Natural syntax! 🎉
Static extension dla built-in types
// Static extensions dla int
public static class IntExtensions
{
// Static factory methods
public static int Parse(this int, string s, int defaultValue = 0)
{
return int.TryParse(s, out int result) ? result : defaultValue;
}
// Static constants
public static int MaxSafeInteger(this int)
{
get => 2147483647;
}
public static int MinSafeInteger(this int)
{
get => -2147483648;
}
}
// Użycie
int parsed = int.Parse("123", defaultValue: 0); // Extension!
int max = int.MaxSafeInteger; // Extension property!
// Static extensions dla DateTime
public static class DateTimeExtensions
{
public static DateTime Today(this DateTime)
{
get => DateTime.Today;
}
public static DateTime Yesterday(this DateTime)
{
get => DateTime.Today.AddDays(-1);
}
public static DateTime Tomorrow(this DateTime)
{
get => DateTime.Today.AddDays(1);
}
}
// Użycie
var today = DateTime.Today; // Extension property!
var yesterday = DateTime.Yesterday; // Extension property!
var tomorrow = DateTime.Tomorrow; // Extension property!
🔥 Extension Blocks - nowa składnia (C# 14)
Problem - rozproszony kod extensions
// Przed C# 14 - osobne static classes dla każdego typu
public static class StringExtensions
{
public static string Reverse(this string str) { /* ... */ }
public static bool IsEmpty(this string str) { get => /* ... */ }
}
public static class ListExtensions
{
public static bool IsEmpty(this List list) { get => /* ... */ }
public static T Last(this List list) { get => /* ... */ }
}
public static class IntExtensions
{
public static bool IsEven(this int n) { get => /* ... */ }
public static bool IsPrime(this int n) { /* ... */ }
}
// Rozproszony kod - trudny w maintanance
// Trzeba pamiętać o naming conventions
// Trzeba pamiętać że class musi być static
Rozwiązanie - Extension Blocks!
🎉 REWOLUCJA C# 14 - Extension Blocks
Nowa składnia extension Type { } - elegancko grupuj extensions!
// C# 14 - extension block!
extension string
{
// Methods
public static string Reverse()
{
return new string(this.Reverse().ToArray());
}
public static int WordCount()
{
return this.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
// Properties
public static bool IsEmpty
{
get => this.Length == 0;
}
public static string Reversed
{
get => this.Reverse();
}
// Operators
public static string operator *(int count)
{
return string.Concat(Enumerable.Repeat(this, count));
}
}
// Użycie - identyczne jak wcześniej
string text = "Hello";
string rev = text.Reverse(); // Method
bool empty = text.IsEmpty; // Property
string repeated = text * 3; // Operator
// Ale kod jest elegancko zgrupowany! ✨
Extension blocks dla generics
// Extension block dla List
extension List
{
// Generic extension property
public static bool IsEmpty
{
get => this.Count == 0;
}
public static T Last
{
get => this[this.Count - 1];
}
public static T FirstOrDefault
{
get => this.Count > 0 ? this[0] : default(T);
set
{
if (this.Count == 0)
this.Add(value);
else
this[0] = value;
}
}
// Generic extension method
public static List Shuffle()
{
var rnd = new Random();
return this.OrderBy(_ => rnd.Next()).ToList();
}
// Static factory
public static List Create(params T[] items)
{
return new List(items);
}
}
// Użycie
var numbers = new List { 1, 2, 3, 4, 5 };
bool empty = numbers.IsEmpty; // Property
int last = numbers.Last; // Property
var shuffled = numbers.Shuffle(); // Method
var created = List.Create(1, 2, 3); // Static factory
Extension blocks dla interfaces
// Extension block dla IEnumerable
extension IEnumerable
{
// Extension property
public static bool Any
{
get => this.Any();
}
public static int Count
{
get => this.Count();
}
// Extension method
public static T[] ToArray()
{
return this.ToArray();
}
public static List ToList()
{
return this.ToList();
}
// Helper methods
public static string JoinString(string separator = ", ")
{
return string.Join(separator, this);
}
}
// Użycie - na WSZYSTKICH IEnumerable!
var numbers = new[] { 1, 2, 3, 4, 5 };
bool any = numbers.Any; // Property extension
int count = numbers.Count; // Property extension
string joined = numbers.JoinString(); // "1, 2, 3, 4, 5"
❌ Stara składnia - rozproszony kod
public static class StringExtensions
{
public static string Reverse(this string str) { }
public static bool IsEmpty(this string str) { get; }
}
public static class StringOperatorExtensions
{
public static string operator *(this string s, int c) { }
}
public static class StringFactoryExtensions
{
public static string Empty(this string) { get; }
}
// 3 klasy dla jednego typu! 😱
✅ Extension block - zgrupowane
extension string
{
// Methods
public static string Reverse() { }
// Properties
public static bool IsEmpty { get; }
// Operators
public static string operator *(int c) { }
// Static members
public static string Empty { get; }
}
// Wszystko w jednym miejscu! ✨
Praktyczne zastosowania
Przykład 1: String helpers - complete toolkit
// Kompletny string extension toolkit w C# 14
extension string
{
// Properties
public static bool IsEmpty { get => this.Length == 0; }
public static bool IsNullOrEmpty { get => string.IsNullOrEmpty(this); }
public static bool IsNullOrWhiteSpace { get => string.IsNullOrWhiteSpace(this); }
public static string Reversed { get => new string(this.Reverse().ToArray()); }
// Methods
public static string Truncate(int maxLength, string suffix = "...")
{
if (this.Length <= maxLength) return this;
return this[..maxLength] + suffix;
}
public static string ToTitleCase()
{
return string.Join(" ",
this.Split(' ')
.Select(word => char.ToUpper(word[0]) + word[1..].ToLower())
);
}
public static bool Contains(string value, StringComparison comparison)
{
return this.Contains(value, comparison);
}
public static int WordCount
{
get => this.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
// Operators
public static string operator *(int count)
{
return string.Concat(Enumerable.Repeat(this, count));
}
// Static helpers
public static string Empty { get => string.Empty; }
public static string NewLine { get => Environment.NewLine; }
}
// Użycie
string text = "hello world from c#";
bool empty = text.IsEmpty; // Property
string title = text.ToTitleCase(); // "Hello World From C#"
string truncated = text.Truncate(10); // "hello worl..."
int words = text.WordCount; // 4
string repeated = "Ha" * 3; // "HaHaHa"
Przykład 2: Collection extensions z operators
// Collections extensions w C# 14
extension List
{
// Properties
public static bool IsEmpty { get => this.Count == 0; }
public static bool IsNotEmpty { get => this.Count > 0; }
public static T First { get => this[0]; }
public static T Last { get => this[this.Count - 1]; }
// Methods
public static List Shuffle()
{
var rnd = new Random();
return this.OrderBy(_ => rnd.Next()).ToList();
}
public static T? SecondOrDefault()
{
return this.Count >= 2 ? this[1] : default;
}
public static List Slice(int start, int end)
{
return this.Skip(start).Take(end - start).ToList();
}
// Operators - combine lists
public static List operator +(List other)
{
var result = new List(this);
result.AddRange(other);
return result;
}
// Operator - remove elements
public static List operator -(List other)
{
return this.Except(other).ToList();
}
// Static factories
public static List Create(params T[] items)
{
return new List(items);
}
public static List Repeat(T item, int count)
{
return Enumerable.Repeat(item, count).ToList();
}
}
// Użycie
var list1 = new List { 1, 2, 3 };
var list2 = new List { 3, 4, 5 };
bool empty = list1.IsEmpty; // Property
int first = list1.First; // Property
int last = list1.Last; // Property
var combined = list1 + list2; // Operator+ : [1, 2, 3, 3, 4, 5]
var difference = list1 - list2; // Operator- : [1, 2]
var shuffled = list1.Shuffle(); // Method
var slice = list1.Slice(1, 3); // [2, 3]
var created = List.Create(1, 2, 3); // Static factory
Przykład 3: DateTime extensions
// DateTime extensions w C# 14
extension DateTime
{
// Properties
public static bool IsWeekend { get => this.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday; }
public static bool IsWeekday { get => !this.IsWeekend; }
public static bool IsToday { get => this.Date == DateTime.Today; }
public static bool IsPast { get => this < DateTime.Now; }
public static bool IsFuture { get => this > DateTime.Now; }
public static string MonthName { get => this.ToString("MMMM"); }
public static int Quarter { get => (this.Month - 1) / 3 + 1; }
// Methods
public static DateTime StartOfDay()
{
return this.Date;
}
public static DateTime EndOfDay()
{
return this.Date.AddDays(1).AddTicks(-1);
}
public static DateTime StartOfMonth()
{
return new DateTime(this.Year, this.Month, 1);
}
public static DateTime EndOfMonth()
{
return this.StartOfMonth().AddMonths(1).AddTicks(-1);
}
public static int DaysUntil(DateTime other)
{
return (other - this).Days;
}
// Operators
public static TimeSpan operator -(DateTime other)
{
return this - other;
}
// Static helpers
public static DateTime Now { get => DateTime.Now; }
public static DateTime Today { get => DateTime.Today; }
public static DateTime Yesterday { get => DateTime.Today.AddDays(-1); }
public static DateTime Tomorrow { get => DateTime.Today.AddDays(1); }
}
// Użycie
var date = new DateTime(2026, 2, 25);
bool weekend = date.IsWeekend; // Property
bool weekday = date.IsWeekday; // Property
string month = date.MonthName; // "February"
int quarter = date.Quarter; // 1
var startDay = date.StartOfDay(); // Method
var endMonth = date.EndOfMonth(); // Method
var yesterday = DateTime.Yesterday; // Static property
var tomorrow = DateTime.Tomorrow; // Static property
Przykład 4: Numeric extensions z INumber<T>
using System.Numerics;
// Generic numeric extensions dla ALL numeric types!
extension T where T : INumber
{
// Properties
public static bool IsZero { get => this == T.Zero; }
public static bool IsPositive { get => this > T.Zero; }
public static bool IsNegative { get => this < T.Zero; }
// Methods
public static T Squared()
{
return this * this;
}
public static T Cubed()
{
return this * this * this;
}
public static T Abs()
{
return T.Abs(this);
}
public static T Clamp(T min, T max)
{
if (this < min) return min;
if (this > max) return max;
return this;
}
// Static helpers
public static T Zero { get => T.Zero; }
public static T One { get => T.One; }
}
// Działa dla WSZYSTKICH numeric types! ✨
int x = 5;
bool zero = x.IsZero; // false
bool positive = x.IsPositive; // true
int squared = x.Squared(); // 25
int cubed = x.Cubed(); // 125
double y = -3.5;
bool negative = y.IsNegative; // true
double abs = y.Abs(); // 3.5
double clamped = y.Clamp(0, 10); // 0
Podsumowanie
Extension Members w C# 14 - kompletna rewolucja w rozszerzalności!
✅ Extension methods (C# 3.0) - przypomnienie, LINQ foundation
✅ 🔥🔥 Extension properties (C# 14) - wreszcie! Get/set support