Tételezzük fel, hogy van egy osztálykönyvtárunk, aminek a forráskódjával nem rendelkezünk. Ebben az esetben azt feltételezhetnénk, hogy a könyvtárban szereplő osztályok funkcionalitását nem tudjuk bővíteni csak örökléssel a saját kódunkban, ha ez engedélyezett és az osztályok nem zártak. (sealed)
De ha nem is zártak, akkor sem biztos, hogy az örököltetés a legjobb mód, mivel elképzelhető, hogy a könyvtár által biztosított osztályokra más metódusok is épülnek, amik nem biztos, hogy kompatibilisek lesznek a bővített, örököltetett típusunkkal.
Ilyen esetben nyújt segítséget az Extension Method nyelvi szolgáltatás, amit magyarul nevezhetnénk bővítő metódusnak is. A bővítő metódusok olyan statikus metódusok, amelyeket nem a metódust definiáló osztályon keresztül érünk el, hanem úgy, mint a bővítés alatt álló típus tag metódusát.
A bővítő metódusok előnye, hogy kerülhetnek külön névtérbe és külön osztálykönyvtárba, így az eredeti bővített típust nem kell újra fordítani és nem kell hozzányúlni, vagyis modulárisan felruházható extra képességekkel, amiket csak használni kell.
A bővítő metódusokra nagyon jó példa a keretrendszerben a LINQ. A LINQ teljes mértékben bővítő metódusokkal van megvalósítva, de erről majd egy picivel később lesz még szó.
A bővítő metódusok szintaxisa a következő:
static class BovitoOsztaly
{
public static [visszateresi_érték] Metodus(this [bovittett_tipus] valozo, ...);
}
A szintaktikából egyértelműen kiderül, hogy minden bővítő metódus egy speciális statikus metódus valójában. A specialitás abban rejlik, hogy a metódus első paramétere a bővíteni kívánt osztály egy példánya, amit megjelölünk a this kulcsszóval. A fordító ebből fogja azt tudni, hogy a metódus elérhető lesz úgy is, mint a bővített típus metódusa. Ez a viselkedés Visual Studio kód kiegészítésben is megjelenik.
Fontos megjegyezni, hogy Extension Method csak statikus osztályban kaphat helyet.
Egy egyszerű bővítő metódusra példa:
using System;
namespace PeldaExtensionmethod
{
public static class Extensions
{
public static void PrintJovoEv(this DateTime date)
{
Console.WriteLine("A jövő év: {0}", date.Year + 1);
}
}
class Program
{
static void Main(string[] args)
{
DateTime.Now.PrintJovoEv();
Console.ReadKey();
}
}
}
A program kimenete:
A jövő év: 2022
C# 14 extensions
Saját kódomban az Extension metódusaimat szeretem típus szerint külön osztályokba csoportosítani, hogy elkerüljem azt a szituációt, hogy egyetlen egy nagy „szemetes” osztályom keletkezzen, ami mindent is bővít. Ennek a szervezésnek a hátránya, hogy ha egyetlen egy metódus keletkezik a típushoz, akkor egy kicsi osztályom van, ami elképzelhető, hogy soha sem fog bővülni.
A C# 14 ezen szituáció áthidalására bevezet egy új szintaxist, az extenson blokkot, amiben egy adott típushoz csoportosítani tudjuk a metódusainkat.
public static class MyExtensions
{
//IEnumerable-re vonatkozó kiterjesztések
extension (IEnumerable<int> elemek)
{
public long LongSum()
{
long sum = 0;
foreach (var item in elemek)
sum += item;
return sum;
}
}
//Listára vonatkozó kiterjesztések
//Lehet generikus is
extension<T>(List<T> source)
{
public void Push(T item)
=> source.Add(item);
public T Pop()
{
T value = source[source.Count - 1];
source.RemoveAt(source.Count - 1);
return value;
}
}
}
Az új szintaxis előnye, hogy bővítő metódusok mellett lehetőségünk van bővítő tulajdonságokat is definiálni. A tuladonságok definiálásásának és használatának szintaxisában nincs eltérés.
//Tulajdonságok
public static class ExtProperties
{
extension(IEnumerable<int> szamok)
{
//Páros és páratlan számok szétválogatása
public IEnumerable<int> Paratlanok
{
get
{
foreach (var item in szamok)
{
if (item % 2 != 0)
yield return item;
}
}
}
public IEnumerable<int> Parosak
{
get
{
foreach (var item in szamok)
{
if (item % 2 == 0)
yield return item;
}
}
}
}
}
Operátorok
Az új szintaxis segítségével static módosítóval ellátott tulajdonságok és metódusok is definiálhatóak, amibe beletartoznak az operátorok is, amelyek a metódusokhoz hasonlóan lehetnek generikusak is. Az alábbi példa a vagy operátort definiálja felül metódusok esetén, hogy úgy viselkedjen, mint a shell-ben megszokott pipe operátor:
public static class OperatorExtension
{
extension(TSource, TResult)
{
public static TResult operator | (TSource source, Func<TSource, TResult> function)
=> function(source);
}
}
Ezen felüldefiniált operátorral F# szerű függvénykompozíciót valósíthatunk meg:
int number = Console.ReadLine() | int.Parse;