V jazyku C# máme již anonymní metody, anonymní typy. Anonymní metody lze navíc zapisovat velmi elegantně pomoci lambda výrazů apod. Co ale nemáme je anonymní implementace interface tj. nemůžeme například psát něco jako toto:
(POZOR: Následující fragment není validní C# kód.)
public static void Main()
{
var descComparer = new IComparer<int>
{
public int Compare(T x, T y)
{
return -1 * Comparer<int>.Default.Compare(x, y);
}
}
var ints = new SortedSet<int>(descComparer) { 5, 9, 2, 11, 1, 4, 10 };
foreach (int i in ints)
{
Console.Write(string.Format(" {0}", i)); //11 10 9 5 4 2 1
}
}
(Pozn.: Kolekce SortedSet<T> je nová v .NET Frameworku 4.0. Od HashSet<T> se liší tím, že interně udržuje přidávané prvky seřazené.)
Anebo můžeme? Ne, toto opravdu není korektní syntaxe jazyka C#. Ono to možná ale zas tolik nevadí, protože existuje technika jak toto obejít a navíc není ve skutečnosti až tak složitá. Jediné co potřebujeme je pro daný interface napsat pomocnou třídu jako jakousi “univerzální” implementaci. Například pro interface IComparer<T> tuto třídu pojmenujeme AnonymousComparer<T> a její implementace bude vypadat takto:
internal sealed class AnonymousComparer<T> : IComparer<T>
{
#region member varible and default property initialization
private readonly Func<T, T, int> compare;
#endregion
#region constructors and destructors
public AnonymousComparer(Func<T, T, int> compare)
{
if (compare == null)
{
throw new ArgumentNullException("compare");
}
this.compare = compare;
}
#endregion
#region IComparer<T> Members
public int Compare(T x, T y)
{
return this.compare(x, y);
}
#endregion
}
Třída jednoduše vyžaduje vždy do svého konstruktoru “nacpat” implementaci všech metod daného interface (zde je jen jedna, ale “pattern” je obecný) jako anonymní metody a pak je jen na příslušných místech volá.
Přestože použití této třídy přímo je sice principiálně možné, pro příklad výše by jsme museli například psát:
var descComparer = new AnonymousComparer<int>((x, y) => -1 * Comparer<int>.Default.Compare(x, y));
je lépe celou třídu ještě následujícím způsobem “zaobalit” a tím úplně skrýt její identitu, protože ta je pouze interní implementační detail:
internal static class Comparer
{
#region member types declaration
private sealed class AnonymousComparer<T> : IComparer<T>
{
#region member varible and default property initialization
private readonly Func<T, T, int> compare;
#endregion
#region constructors and destructors
public AnonymousComparer(Func<T, T, int> compare)
{
this.compare = compare;
}
#endregion
#region IComparer<T> Members
public int Compare(T x, T y)
{
return this.compare(x, y);
}
#endregion
}
#endregion
#region action methods
public static IComparer<T> Create<T>(Func<T, T, int> compare)
{
if (compare == null)
{
throw new ArgumentNullException("compare");
}
return new AnonymousComparer<T>(compare);
}
#endregion
}
(Pozn.: Kontrola argumentu se nám zde přesunula ze samotné třídy už do metody Create().)
Zavedením statické metody Create() jsme u generického interface zároveň umožnili využít v některých případech type inferenci (u příkladu výše to zrovna moc nejde, tam musíme uvést typy argumentů x a y). Zároveň si všimněte, že se nyní skutečně vrací pouze typ IComparer<T> (nikoliv konkrétní třída).
Konečná podoba našeho příkladu tedy je:
public static void Main()
{
var descComparer = Comparer.Create((int x, int y) => -1 * Comparer<int>.Default.Compare(x, y));
var ints = new SortedSet<int>(descComparer) { 5, 9, 2, 11, 1, 4, 10 };
foreach (int i in ints)
{
Console.Write(string.Format(" {0}", i)); //11 10 9 5 4 2 1
}
}
Skoro je možné tvrdit, že tento zápis je možná i o něco přehlednější než původní zápis s použitím neexistující syntaxe.
Zkusme ještě “univerzální” implementaci pro interface s více metodami např. IEqualityComparer<T>:
internal static class EqualityComparer
{
#region member types declaration
private sealed class AnonymousEqualityComparer<T> : IEqualityComparer<T>
{
#region member varible and default property initialization
private readonly Func<T, T, bool> equals;
private readonly Func<T, int> getHashCode;
#endregion
#region constructors and destructors
public AnonymousEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
{
this.equals = equals;
this.getHashCode = getHashCode;
}
#endregion
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
{
return this.equals(x, y);
}
public int GetHashCode(T obj)
{
return this.getHashCode(obj);
}
#endregion
}
#endregion
#region action methods
public static IEqualityComparer<T> Create<T>(Func<T, T, bool> equals, Func<T, int> getHashCode)
{
if (equals == null)
{
throw new ArgumentNullException("equals");
}
if (getHashCode == null)
{
throw new ArgumentNullException("getHashCode");
}
return new AnonymousEqualityComparer<T>(equals, getHashCode);
}
#endregion
}
Je vidět, že ukázaný postup je opravdu obecný, ale doporučil bych ho používat pouze pokud je interface jednoduchý tj. pokud má tak maximálně do tří až čtyř metod. Jinak bych asi implementaci umístil do konkrétní třídy.
Naopak, protože interface s pouze jednou metodou je sémanticky naprosto totožný s delegátem (pro kterého běžně používáme anonymní metody), je pro takový interface tento postup téměř ideální.
Příště: Nejen další příklad použití tohoto postupu u Disposables.