Generikus metódus hívása futásidejű típusparaméterrel

//NOTE: a devportal OneTime bindinggal húzza be a postokat. 🙂 Mivel elég gyakori, hogy a publikálás után veszem észre, hogy összekevertem a télapó és a gestapo szavakat, hiába javítom, a devportalon nem változik. Javaslom, hogy aki neadjbéla arra vetemedik, hogy szerény kis agymenéseimmel múlatja az időt, kattanjon a postok alján lévő wordpress linkre, ott lesz a friss változat. Danke sün!

A generikus metódusokat/típusokat szeretjük, mert nagy flexibilitást, jobb teljesítményt és nagyobb típusbiztonságot adnak. Azonban semmi sincs ingyen – van, ahol nagyon meg tudja keseríteni az életünket az átállás generikus metódusokra. Vegyük a következő példát…

Jussunk el a problémáig!

Adott egy Arena nevű osztály, mely rendelkezik egy Harc nevű metódussal, amely két típusparaméterrel rendelkezik, továbbá megköti, hogy ezen T1 és T2 típusoknak kell, hogy legyen paramétermentes konstruktoruk, és meg kell valósítaniuk az IHarcképes interfészt. C#-ul mondva:

class Arena
{
    public string Harc<T1, T2>()
        where T1 : IHarcképes, new()
        where T2 : IHarcképes, new()
    {
        T1 t1 = new T1();
        T2 t2 = new T2();

        int delta = t1.GetHarcérték() - t2.GetHarcérték();
        if (delta > 0) return typeof(T1).Name + " az erősebb.";
        if (delta < 0) return typeof(T2).Name + " az erősebb.";
        return "Döntetlen.";
    }
}

interface IHarcképes
{
    int GetHarcérték();
}

Ahogy látható, a metódus létrehoz két objektumot, és az interfészmetódus segítségével megvizsgálja, melyik nyerne egy harcban. Tegyük fel, hogy sok osztályunk van, amelyeknek harcolniuk kell majd. Emellett szeretnénk ezeket párokba rendezni, mégpedig olyan módon, hogy a párokba rendezés dinamikusan történhessen, tehát ne legyen előre kőbe vésve. Néhány osztály, amivel el tudunk indulni:

class Katona : IHarcképes
{
    public int GetHarcérték() { return 1; }
}

class Ellenség : IHarcképes
{
    public int GetHarcérték() { return 2; }
}

class Lábas : IHarcképes
{
    public int GetHarcérték() { return 1; }
}

class Ellenlábas : IHarcképes
{
    public int GetHarcérték() { return 2; }
}

Érezhető, hogy a katona ellensége az ellenség lesz, illetve beigazolódik az is, hogy a konyhában minden lábasnak van egy ellenlábasa. (Maksa ftw!) Normál esetben a harcértéket valamiből kiszámolnánk; ehelyett itt sony-típusú randomszámokat adunk vissza. A párokba rendezést bízzuk egy osztályra:

class HarcpárTároló
{
    Dictionary<Type, Type> pairs = new Dictionary<Type, Type>();

    public HarcpárTároló()
    {
        pairs.Add(typeof(Katona), typeof(Ellenség));
        pairs.Add(typeof(Lábas), typeof(Ellenlábas));
    }

    public Type GetPairTypeFromType(Type t)
    {
        return pairs[t];
    }
}

Ebből gyárthatunk több leszármazottat is, és mindegyik a saját kis logikája alapján pároztathatja párosíthatja a típusokat. Eddig szép és jó. De hogyan hívjuk meg a Harc metódust? Próbáljuk meg valahogy…

Arena arena = new Arena();
HarcpárTároló hpt = new HarcpárTároló();

object egyik = new Lábas();
Type masik = hpt.GetPairTypeFromType(egyik.GetType());

arena.Harc<egyik.GetType(), masik>();  //compile-time error

Ez ugye fájdalmasan csúnya. Olyannyira, hogy le se fordul. (Sőt, ha valaki megpróbálta begépelni, érezhette, hogy az IntelliSense mindent megtesz, hogy akadályozza.) A probléma ugyebár az, hogy a generikus metódus meghívásához fordítási időben rendelkezésre kellene állnia a típusparamétereknek. Mit lehet tenni?

Tükrözés!

Talán ismerős lehet, hogy ismeretlen típusoknál a reflection segítségével elérhetünk ismeretlen metódusokat. De természetesen arra is képes a rendszer, hogy ismert típusok ismert metódusait adja vissza. 🙂

A metódus kiválasztása után csak plusz egy lépést kell elvégezni ahhoz, hogy működjön a dolog. Mivel itt generikus metódusról van szó, a metódus kiválasztása csupán a “nyílt” metódust adja vissza. Ez a generikus sablon, amit tulajdonképpen megírtunk C#-ban. Ahhoz, hogy ezt két konkrét típussal használhassuk, rá kell venni a fordítót, hogy készítse el a metódus nem-generikus változatát. (Ha a nyílt metódust hívnánk, egy InvalidOperationExceptiont kapnánk, ami arról tájékoztatna, hogy ez egy nyílt generikus metódust, és mint ilyen, meghívhatatlan. :)) Végül pedig ugyanúgy hívhatjuk a metódust, mintha bármilyen nem-generikus metódust hívnánk reflectionön keresztül: a zárt metódus már nem generikus.

MethodInfo openMeth = arena.GetType().GetMethod("Harc");
MethodInfo closedMeth = openMeth.MakeGenericMethod(
    new[] { egyik.GetType(), masik });
closedMeth.Invoke(arena, null);

Nem olyan szép, mintha meglennének előre a típusok, de ez így képes feldolgozni, ha csak futásidőben kapjuk meg őket. Készen vagyunk.

Lehetne ezt máshogy?

Természetesen: senki nem kötelez minket arra, hogy generikus metódust használjunk ott, ahol valami miatt kényelmetlen. A Harc metódust megírhatjuk típusmentesre is. Picit bonyolultabb lesz.

AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
public string HarcNonGeneric(Type t_t1, Type t_t2)
{
    if (!t_t1.GetInterfaces().Contains(typeof(IHarcképes)) ||
        !t_t2.GetInterfaces().Contains(typeof(IHarcképes)))
    {
        throw new InvalidOperationException(
            "Mindkét paramétertípus meg kell valósítsa az IHarcképes interfészt.");
    }

    IHarcképes t1 = (IHarcképes)Activator.CreateInstance(t_t1);
    IHarcképes t2 = (IHarcképes)Activator.CreateInstance(t_t2);

    int delta = t1.GetHarcérték() - t2.GetHarcérték();
    if (delta > 0) return t_t1.Name + " az erősebb.";
    if (delta < 0) return t_t2.Name + " az erősebb.";
    return "Döntetlen.";
}

Jobb ez a metódus? Röviden: nem, a generikus mindig szebb. 🙂 De ha objektívebben nézzük: le kell mérni, melyik mit tud.

AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English

Egymillió hívásnál…

Lefuttattam a generikus és a nemgenerikus változat meghívását 1 milliószor, és a kiváncsiság kedvéért a nemgenerikusat reflectionön keresztül is. Ha csak a metódushívásokat nézzük, előre látható, hogy a generikus változat szanaszét veri a másik kettőt. (ms, az alacsonyabb érték a jobb.)

Generic, reflection: 12091
NonGeneric, reflection: 20399
NonGeneric, nonreflection: 16904

Ha mindig, vagy legalábbis sokszor kell ugyanazokkal a típusokkal dolgozunk, ugyanarra a típusra lefuttatnunk a metódust, akkor tehát megéri generikusan implementálni. A metódus elkészítésének overheadje csak egy egyszeri overhead. (A teljesség kedvéért megjegyzem, hogy a gép egyéb dolgaitól függően volt egy 1-3 mp-es kilengés a futtatások között, de az arányok mindig így alakultak.)

Mi van azonban, ha egy típusra (típuspárra) csak egyszer futtatjuk le, és megyünk is tovább a következő típusra/típuspárra?

Generic, reflection: 25157
NonGeneric, reflection: 22699
NonGeneric, nonreflection: 16916

Mivel minden egyes metódushívás előtt le kell kérni a metódust, és legyártatni a zárt változatát, megfordult a dolog: a nem-generikus metódus itt minden hátránya ellenére is sokkal gyorsabb, mint a generikus. Tehát ilyen esetekre jobban járunk, ha nem generikusat készítünk, hanem maradunk a “jól bevált” C# 1.0-s módszernél. Persze most Activatorral dolgoztunk – ha ennek a tudása nem elég. és ConstructorInfokon keresztül kell dolgozni, az okozhat némi lassulást a nemgenerikus változatban.

Mára ennyi, most pedig vissza kódolni.

AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDetect languageDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddishAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBulgarianCatalanChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekHaitian CreoleHebrewHindiHungarianIcelandicIndonesianIrishItalianJapaneseKoreanLatinLatvianLithuanianMacedonianMalayMalteseNorwegianPersianPolishPortugueseRomanianRussianSerbianSlovakSlovenianSpanishSwahiliSwedishThaiTurkishUkrainianUrduVietnameseWelshYiddish

English (auto-detected) » English
Reklámok

~ Szerző: Fülöp Dávid - 2011. április 29..

3 hozzászólás to “Generikus metódus hívása futásidejű típusparaméterrel”

  1. Na, nem próbálod ki IsAssignableFrom()-mal, ahogy a devportalon írtam kommentben? Kíváncsi vagyok.

  2. Hali!
    Deeee, máris, köszi a tippet! 🙂 (Csak nem néztem a devportalt, és vmiért emailt se küld, ha oda érkezik egy komment. :/)

  3. Na, az (egy) eredmény:
    Generic, reflection: 11158
    NonGeneric, reflection: 20623
    NonGeneric, nonreflection: 16495
    NonGeneric, nonreflection, isassignablefrom: 12952
    (A többi is kb így alakult, a korábbi 1-2mp-es kilengéssel.)
    Nagyon megdobja a metódus futását, és még nem ismertem, úgyhogy ma is tanultam valamit, köszönöm! 🙂

    Az activator helyetti megoldás eléggé barna/feketeöves, de hétvégén, ha lesz időm, implementálom. Azzal együtt már lehet, hogy teljesen behozza a hátrányát a nemgenerikus változat. Onnantól pedig végüllis kár lesz ilyen helyzetekre generikus metódust írni, elég ha futásidőben írunk IL kódot. :)))

    Még1x kösz a tippet!

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s

 
%d blogger ezt kedveli: