Anonymní implementace interface

Tomáš Holan       21. 1. 2011       C#       6518 zobrazení

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.

 

hodnocení článku

0 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback