C# 3.0: Extension methods

Két héttel ezelőtt a CardSpace kapcsán írtam arról, hogy milyen egyszerűen lehet JavaScriptben egy már meglévő osztályhoz újabb metódust keverni. A jó hír az, hogy ezt nem csak JavaScriptben lehet, hanem C#-ban is. Pontosabban csak lehet majd, mert az az ún. extension method feature csak a következő verzióban lesz megtalálható.

Aki ki szeretné próbálni az alábbi példákat töltse le például a LINQ 2006. májusi előzetesét, az ugyanis tartalmazza a csc.exe 3.0 verzióját és VS projekt sablonokat is, így kényelmesen kísérletezhetünk. Előre szólok, hogy innentől kezdve elfelejthetjük a Studioban a smart tag-eket és az IntelliSense sem lesz tökéletes, tehát inkább virtuális gépben kísérletezzünk!

Visszatérve: az extension method célja, hogy újabb metódusokat vehessünk hozzá olyan típusokhoz, amelyeket nem mi írtunk, de egy adott funkció leginkább oda kapcsolódna. Sokan vitatkoznak arról, hogy erre valóban van-e szükség, hogy meg lehet-e oldani ezeket a problémákat örökléssel vagy statikus metódusokkal és hogy átláthatatlanságot eredményez a forráskódban. Ez egy kényelmi szolgáltatás, akkor használjuk, amikor valóban kell!

Például webes fejlesztésnél gyakran felmerül az igény, hogy egy sztringet hátéemelenkódolni kell. Ha ezt nem közvetlenül webes projektben tesszük meg, akkor kell egy referencia a System.Web.dll-re, usingolni kell a System.Web névteret és használni kell a HttpUtility.HtmlEncode() statikus metódust. Macera! Nosza ragasszuk hozzá ezt a funkciót a System.String osztályhoz, így erőlködés nélkül mindig kéznél lesz! Íme a szintaktika:

    using System;
    using System.Web;

    namespace MyExtensions
    {
        public static class MyExtensionMethods
        {
            public static string HtmlEncode( this string s )
            {
                return HttpUtility.HtmlEncode( s );
            }
        }
    }

Aláhúztam a lényeget:

  • Kell egy névtér, erre később hivatkozni fogunk usinggal.
  • Kell egy statikus osztály, ebben tudunk csak kiterjesztő metódusokat készíteni.
  • A kiterjesztő metódusunk egy statikus metódus, aminek első paramétere az a típus, amit ki akarunk terjeszteni, ezt pedig a this kulcsszóval jelezzük. Lényegében ettől lesz ő extension method, így a fordító tudni fogja, hogy ezt a paramétert nem kell átadnunk.

Nincs más hátra, mint hogy használjuk is mindezt:

    using System;
    using MyExtensions;

    namespace MyTester
    {
        public class Program
        {
            public static void Main( string[] args )
            {
                string text = "<script>alert('U R HaCk3d!')</script>";
                Console.WriteLine( text.HtmlEncode() );
            }
        }
    }

Innentől kezdve tehát van a string osztálynak egy HtmlEncode metódusa, ami az aktuális példányon végez műveletet, amit természetesen nem adunk át neki, bármennyire is ezt sugallja a függvény paraméterezése. A fordító azért fogja felismerni ezt a metódust, mert a fájl elején ott van a using direktíva a metódust tartalmazó névtérre.

Na de mi van akkor, ha mi nem csak egy típust akarunk kiterjeszteni, hanem mindet? Persze kiterjeszthetjük a System.Object osztályt is a fenti módon, működik. Sőt, ha mindezt a System névtérben végezzük el, akkor azonnal mindenhol ott is lesz. Kövessük el például ezt, adjunk egy Print metódust mindenkinek:

    namespace System
    {
        public static class MyObjectExtensions
        {
            public static void Print( this object o )
            {
                Console.WriteLine( o );
            }
        }
    }

Ezzel természetesen kibővítettük az előző példában használt string típusú text nevű változót is, így már írhatunk ilyet:

    text.Print();

Sőt, akár ilyet is:

    text.HtmlEncode().Print();

Van persze egy szebb megoldás, használhatunk generikus típusokat! Hogyan lehetne például azt megoldani, hogy típustól függetlenül egységesen vizsgáljuk meg, hogy egy adott változó "alapértéken" van-e? Naná, hogy generikus típusokkal és kiterjesztő metódusokkal, például így:

    public static bool IsDefault<T>( this T x )
    {
        return x == null ? true : x.Equals( default( T ) );
    }

Ettől kezdve bármely típuson meghívhatjuk a generikus IsDefault<T> metódust, a szintaktika teljesen azonos, például:

    Guid g = Guid.Empty;
    g.IsDefault<Guid>().Print();

    int i = 0;
    i.IsDefault<int>().Print();

    string s = null;
    s.IsDefault<string>().Print();

A DateTime nem szerepel a fenti példában, mert nem tudtam fejből, hogy mi a default értéke. Korábbi bűvészkedésünknek hála, már egyszerű volt megtudni:

    // Íme az alapértelmezett dátum:
    default( DateTime ).Print();

Jópofa, nemde? smile_wink

 

Technorati tags: , ,

2 thoughts on “C# 3.0: Extension methods

Leave a reply to Zsolt Cancel reply