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:
- Utworzenie statycznej klasy widocznej dla klas, które mają z rozszerzonych obiektów korzystać (np.
MyExtensions
) - Utworzenie statycznej metody z widocznością przynajmniej taką, jak opakowująca ją klasa.
- Pierwszym parametrem tej metody musi być obiekt klasy rozszerzanej poprzedzony słowem kluczowym
this
. - 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:
Dziwne, że Microsoft nie wpadł na reklamowanie tego feature’a rozsyłając masowo maile „Dear developer! Extend your possibilities! Try our new offer..”
Jedna odpowiedź Zostaw komentarz
W oparciu o metody rozszerzające działa LINQ i wychodzi mu to całkiem nieźle 😉