Gdzie i w jaki sposób stosować słowo kluczowe delegate oraz w jaki sposób zastąpić wywołanie funkcji typem delegatu lub wyrażeniem lambda?
Gdzie i w jaki sposób stosować słowo kluczowe delegate oraz w jaki sposób zastąpić wywołanie funkcji typem delegatu lub wyrażeniem lambda?
Typ delegatu (Delegate) pozwala na oddzielenie wywołania metody od miejsca jej wskazania. Pozwalają na luźniejsze powiązanie między kodem wywołującym a kodem definiującym, co powinno zostać wywołane (jaka metoda) przy zachowaniu metadanych - liczby oraz typu parametrów oraz typu wartości zwrotnej.
Nasze rozważania rozpoczniemy od uproszczonego przykładu - w aplikacji potrzebujemy funkcji logującej komunikaty - funkcja taka powinna przyjmować parametr tekstowy z komunikatem i nie powinna zwracać żadnej wartości. W tym celu budujemy i wywołujemy metodę LogMessage:
class Program { static void Main(string[] args) { LogMessage("start"); LogMessage("running"); LogMessage("stop"); } static void LogMessage(string message) { Console.WriteLine(message); } }
Wywołując metodę LogMessage w programie, używamy wywołania po nazwie funkcji, co oznacza, że np. przy zastąpieniu funkcji LogMessage funkcją LogMessageToFile musielibyśmy zmienić wszystkie odwołania w aplikacji. Aby uniknąć tej sytuacji, możemy użyć typu delegatu deklarując go początkowo w aplikacji i przypisując wywołanie poprzez zadeklarowany delegat:
class Program { delegate void LogMsgDelegate(string m); static void Main(string[] args) { LogMsgDelegate log = LogMessage; log("start"); log("running"); log("stop"); } static void LogMessage(string message) { Console.WriteLine(message); } }
W powyższym przykładzie słowo kluczowe delegate zostało użyte do zdefiniowania typu delegatu przyjmującego jeden parametr typu string i nie zwracającego wartości (void). Typ delegatu pozwala na prostą modyfikację metody logowania i użycie LogMessageToFile:
class Program { delegate void LogMsgDelegate(string m); static void Main(string[] args) { LogMsgDelegate log = LogMessageToFile; log("start"); log("running"); log("stop"); } static void LogMessage(string message) { Console.WriteLine(message); } static void LogMessageToFile(string mesage) { System.IO.File.AppendAllText(@"D:\tmp\log.txt", mesage + Environment.NewLine); } }
Załóżmy, że w naszej aplikacji chcielibyśmy równocześnie logować komunikaty na konsolę i do pliku, w takiej sytuacji możemy dodać kolejną metodę do naszej aplikacji - LogMessageAll - wywołującą istniejące metody logowania oraz przypisać ją do zmiennej delegacyjnej log:
class Program { delegate void LogMsgDelegate(string m); static void Main(string[] args) { LogMsgDelegate log = LogMessageAll; log("start"); log("running"); log("stop"); } static void LogMessageAll(string message) { LogMessage(message); LogMessageToFile(message); } static void LogMessage(string message) { Console.WriteLine(message); } static void LogMessageToFile(string mesage) { System.IO.File.AppendAllText(@"D:\tmp\log.txt", mesage + Environment.NewLine); }
Nie zawsze jednak w aplikacji chcemy tworzyć kolejną składową tylko po to, aby wywołać kombinację już istniejących, zamiast tego możemy użyć słowa kluczowego delegate do utworzenia metody anonimowej zastępującej pełną metodę:
class Program { delegate void LogMsgDelegate(string m); static void Main(string[] args) { LogMsgDelegate log = delegate(string message) { LogMessage(message); LogMessageToFile(message); }; log("start"); log("running"); log("stop"); } static void LogMessage(string message) { Console.WriteLine(message); } static void LogMessageToFile(string mesage) { System.IO.File.AppendAllText(@"D:\tmp\log.txt", mesage + Environment.NewLine); } }
W ten sposób zastąpiliśmy metodę LogMessageAll metodą anonimową, korzystając ze słowa kluczowego delegate i zgodnej z typem delegatu definicji tej metody (z jednym parametrem string i nie zwracającej wartości). Przykład ten pokazuje, iż słowo kluczowe delegate poza deklaracją typu delegatu pozwala również na definiowanie metod anonimowych. Można powiedzieć, że metoda anonimowa skróciła zapis wywołania w stosunku do pełnej metody. Samą metodę anonimową możemy również zastąpić wyrażeniem lambda nieznacznie modyfikując kod przypisania zmiennej log:
class Program { delegate void LogMsgDelegate(string m); static void Main(string[] args) { LogMsgDelegate log = (string message) => { LogMessage(message); LogMessageToFile(message); }; log("start"); log("running"); log("stop"); } static void LogMessage(string message) { Console.WriteLine(message); } static void LogMessageToFile(string mesage) { System.IO.File.AppendAllText(@"D:\tmp\log.txt", mesage + Environment.NewLine); } }
Podsumowując, można stwierdzić, że słowo kluczowe delegate pozwala na deklarację typów delegatów oraz definiowanie metod anonimowych. Metody anonimowe oraz ich uproszczona konstrukcja w postaci wyrażeń lambda pozwalają na uproszczenie definicji typów przez ograniczenie ilości nazwanych składowych. Typy delegacyjne uożliwiają luźne powiązania między metodami oraz ich wywołaniami, a także ułatwiają utrzymywanie złożonych aplikacji.