Short Tip #03: Extension Methods

Dla każdego programisty błogosławieństwem są (a przynajmniej powinny być) gotowe biblioteki. Sam regularnie korzystam z STL# ;), czyli System.Collections.Generic. Są to skompilowane klasy, jednak .NET 3.5 (a dokładniej C# 3.0) dostarcza możliwość dodania do nich własnoręcznie zaimplementowane metody. Konstrukcja ta nosi nazwę Extension Methods, a korzysta się z niej w następujący sposób:

  1. Utworzenie statycznej klasy widocznej dla klas, które mają z rozszerzonych obiektów korzystać (np. MyExtensions)
  2. Utworzenie statycznej metody z widocznością przynajmniej taką, jak opakowująca ją klasa.
  3. Pierwszym parametrem tej metody musi być obiekt klasy rozszerzanej poprzedzony słowem kluczowym this.
  4. Voila! Można korzystać z metody wywołując ją bezpośrednio na rozszerzonym obiekcie. Przykład poniżej.
public static class MyExtensions
{
    public static float CelsiusToFahrenheit(this float degree)
    {
        return (degree * 9.0f/5.0f) + 32.0f;
    }
    public static float FahrenheitToCelsius(this float degree)
    {
        return (degree - 32.0f) * 5.0f / 9.0f;
    }
}

Powyższy kod rozszerza typ wbudowany float o metody CelsiusToFahrerheit() oraz FahrenheitToCelsius() pozwalające na przeliczanie wartości temperaturowych. Korzystanie z nowopowstałych metod jest trywialne (cała magia w linii 6):

private void MyMethod()
{
    // get temperature in Celsius degrees
    float temperature = GetTemperatureFromThermometer();
    // convert to Fahrenheit
    temperature = temperature.CelsiusToFahrenheit();
    // process the temperature
}

Nie bez przyczyny wspomniałem o przestrzeni nazw System.Collections.Generic. Klasy w niej zawarte można również rozszerzyć o nowe metody. Na pierwszy rzut oka przeszkodą wydaje się być fakt, że klasy te są szablonami, ale można to ładnie opakować:

public static class MyGenericExtensions
{
    public static T GetMax<T>(this List<T> l) where T : IComparable
    {
        if (l == null) throw new NullReferenceException();
        if (l.Count == 0) throw new ArgumentException();
        T res = l[0];
        foreach (var item in l)
            if (item.CompareTo(res) > 0)
                res = item;
        return res;
    }
}

i użycie powyższego kodu (metoda RandomString(int length) została zaczerpnięta ze strony http://www.c-sharpcorner.com/UploadFile/mahesh/RandomNumber11232005010428AM/RandomNumber.aspx):

using System;
using System.Collections.Generic;
using System.Text;

namespace ExtensionMethods
{
    class MyClass : IComparable
    {
        private int PrimaryParameter;
        private int SecondaryParameter;

        public MyClass(Random r)
        {
            PrimaryParameter = r.Next(10);
            SecondaryParameter = r.Next(10);
        }

        public int CompareTo(object obj)
        {
            MyClass other = obj as MyClass;
            if (other == null) throw new ArgumentException("Object is not MyClass");
            if (PrimaryParameter > other.PrimaryParameter)
                return 1;
            else return (PrimaryParameter < other.PrimaryParameter) ? -1 : SecondaryParameter.CompareTo(other.SecondaryParameter);
        }

        public override string ToString()
        {
            return string.Format("[{0}, {1}]", PrimaryParameter, SecondaryParameter);
        }
    }

    class Program
    {
        private static string RandomString(int size, Random r)
        {
            StringBuilder builder = new StringBuilder();
            char ch;
            for (int i = 0; i < size; i++)
            {
                ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * r.NextDouble() + 65)));
                builder.Append(ch);
            }
            return builder.ToString();
        }

        static void Main(string[] args)
        {
            List<int> iList = new List<int>();
            List<float> fList = new List<float>();
            List<string> sList = new List<string>();
            List<MyClass> mcList = new List<MyClass>();
            Random r = new Random();

            for (int i = 0; i < 10; ++i)
            {
                r = new Random(r.Next());
                iList.Add(r.Next(50));
                fList.Add((float)(r.NextDouble()*100));
                sList.Add(RandomString(r.Next(8) + 2, r));
                mcList.Add(new MyClass(r));
            }
            var iMax = iList.GetMax();
            var fMax = fList.GetMax();
            var sMax = sList.GetMax();
            var mcMax = mcList.GetMax();

            Console.WriteLine("int:");
            foreach (var item in iList) Console.Write(item + ", ");
            Console.WriteLine();
            Console.WriteLine("int max: {0}", iMax);
            Console.WriteLine();

            Console.WriteLine("float:");
            foreach (var item in fList) Console.Write(item + ", ");
            Console.WriteLine();
            Console.WriteLine("float max: {0}", fMax);
            Console.WriteLine();

            Console.WriteLine("string:");
            foreach (var item in sList) Console.Write(item + ", ");
            Console.WriteLine();
            Console.WriteLine("string max: {0}", sMax);
            Console.WriteLine();

            Console.WriteLine("MyClass:");
            foreach (var item in mcList) Console.Write(item + ", ");
            Console.WriteLine();
            Console.WriteLine("MyClass max: {0}", mcMax);
            Console.WriteLine();

            Console.ReadKey();
        }
    }
}

Na potrzeby przykładu stworzyłem klasę MyClass implementującą interfejs IComparable, żeby pokazać, że dla niewbudowanych typów danych ta metoda również działa. RandomString jest na tyle zrozumiałe, że nie będę tej metodzie poświęcał więcej uwagi.

Stworzone zostały 4 listy (linijki 53-56), każda zawierająca obiekty różnego typu, nie tylko proste liczby (int, float), dla których porównanie jest intuicyjne, ale też string oraz obiekty MyClass, dla których instrukcję porównania możemy napisać w sposób dowolny.

Każda lista zostaje zasilona 10 losowymi (powiedzmy) elementami, po czym w liniach (67-70) wywołujemy wcześniej napisaną metodę rozszerzającą. Kolejne linie to prezentacja wyniku w oknie konsoli, u mnie wygląda to następująco:

ExtensionMethods

Dziwne, że Microsoft nie wpadł na reklamowanie tego feature’a rozsyłając masowo maile „Dear developer! Extend your possibilities! Try our new offer..”

Be Sociable, Share!
czoper opublikowano dnia 2010-3-19 Kategoria: Programowanie | Tagi:, ,

Jedna odpowiedź Zostaw komentarz

  1. #1Xion @ 2010-3-19 12:59

    W oparciu o metody rozszerzające działa LINQ i wychodzi mu to całkiem nieźle 😉

Zostaw odpowiedź

(Ctrl + Enter)