Kiedyś już wspomniałem o słowie kluczowych dynamic. W tym i następnych wpisach chciałbym przedstawić praktyczne zastosowanie tego mechanizmu. Na koniec wyjaśnię, jak dynamic jest zaimplementowany przez CLR i jak bardzo spowalnia aplikację…
Programiści używający ASP.NET MVC z pewnością rozpoznają zasadę działania ExpandoObject. Klasa umożliwia tworzenie dynamicznych kontenerów. Na przykład:
private static void Main(string[] args) { dynamic bag = new ExpandoObject(); bag.FirstName = "Piotr"; bag.LastName = "Zielinski"; DisplayBag(bag); } private static void DisplayBag(dynamic bag) { Console.WriteLine(bag.FirstName); Console.WriteLine(bag.LastName); }
ExpandoObject umożliwia doczepianie nowych elementów do klasy. Gdy użytkownik próbuje zapisać coś w właściwości, nowe pole jest dodawane dynamicznie. Możliwe jest również tworzenie metod:
dynamic bag = new ExpandoObject(); bag.FirstName = "Piotr"; bag.LastName = "Zielinski"; bag.Print = (Action) (() => { Console.WriteLine(bag.FirstName); Console.WriteLine(bag.LastName); }); bag.Print();
ExpandoObject implementuje IDictionary<string,object>, który przechowuje dodane właściwości i metody. Takim sposobem można usuwać wcześniej dodane elementy tzn.:
dynamic bag = new ExpandoObject(); bag.FirstName = "Piotr"; bag.LastName = "Zielinski"; bag.Print = (Action) (() => { Console.WriteLine(bag.FirstName); Console.WriteLine(bag.LastName); }); ((IDictionary<string, object>) bag).Remove("FirstName"); bag.Print();
Kod zakończy się wyjątkiem podczas wykonywania Console.WriteLine(bag.FirstName) ponieważ ta właściwość została usunięta w przedostatniej linii kodu.
Nie jestem pewny co ASP.NET MVC używa wewnętrznie ale taki scenariusz na ExpandoObject jest jak najbardziej trafny. Inny scenariusz znalazłem na jednym z blogów MSDN (Alexandra Rusina) a mianowicie uproszczenie składni LINQ Tlo XML. Wyobraźmy sobie, że mamy:
XElement contacts = new XElement("Contacts", new XElement("Contact", new XElement("Name", "Patrick Hines"), new XElement("Phone", "206-555-0144"), new XElement("Address", new XElement("Street1", "123 Main St"), new XElement("City", "Mercer Island"), new XElement("State", "WA"), new XElement("Postal", "68042") ) ) );
Kod bardzo nieczytelny a jeszcze trudniejszy w pisaniu zapytań. Za pomocą ExpandoObject można to uprościć do:
dynamic contact = new ExpandoObject(); contact.Name = "Patrick Hines"; contact.Phone = "206-555-0144"; contact.Address = new ExpandoObject(); contact.Address.Street = "123 Main St"; contact.Address.City = "Mercer Island"; contact.Address.State = "WA"; contact.Address.Postal = "68402";
Wyświetlenie ulicy teraz jest dużo prostsze i sprowadza się do:
Console.WriteLine(contact.Address.Street);
W LINQ to XML musielibyśmy:
Console.WriteLine((string)contactXML.Element("Address").Element("Street"));
Podobnie można tworzyć kolekcje List<ExpandoObject> zamiast korzystać XElement.Descendants. Szczególnie zapytania LINQ wyglądają krócej. W LINQ To XML musielibyśmy pisać:
var phonesXML = from c in contactsXML.Elements("Contact") where c.Element("Name").Value == "Patrick Hines" select c.Element("Phone").Value;
Za pomocą dynamic wystarczy:
var phones = from c in (contacts as List<dynamic>) where c.Name == "Patrick Hines" select c.Phone;
Autorka wspomnianego blogu również pokazuje jak można skonwertować ExpandoObject (który oczywiście nie ma nic wspólnego z LINQ to XML) do XElement:
private static XElement expandoToXML(dynamic node, String nodeName) { XElement xmlNode = new XElement(nodeName); foreach (var property in (IDictionary<String, Object>)node) { if (property.Value.GetType() == typeof(ExpandoObject)) xmlNode.Add(expandoToXML(property.Value, property.Key)); else if (property.Value.GetType() == typeof(List<dynamic>)) foreach (var element in (List<dynamic>)property.Value) xmlNode.Add(expandoToXML(element, property.Key)); else xmlNode.Add(new XElement(property.Key, property.Value)); } return xmlNode; }
Myślę, że LINQ To XML jest dobrym przykładem na nieprzejrzyste API. Niestety autorzy prawdopodobnie nie mieli wyboru ponieważ LINQ TO XML powstał dużo wcześniej niż dynamic. Warto jednak wciąć pod uwagę programowanie dynamiczne projektując różnego rodzaju Frameworki.
`Myślę, że LINQ To XML jest dobrym przykładem na nieprzejrzyste API.` – nie zgodzę się, LINQ to XML daleko do nieprzejrzystego API – twórcy robili co mogli, po prostu w statycznie typowanym środowisku dużo lepiej niż wrzucanie wszędzie brzydkich stringów się nie da.
A bronię LINQ to XML, bo dzięki niemu mogłem przestać psuć sobie życie za pomocą XmlDocument: ciekawe porównanie np. tutaj: http://msdn.microsoft.com/pl-pl/library/bb387021.aspx
Warto też dodać, że dzięki temu, że ExpandoObject możemy traktować jako słownik to istnieje możliwość dynamicznego dodawania nowych właściwości za pomocą tekstu:
var x = new ExpandoObject() as IDictionary;
x.Add(“FirstName”, “John”);
x.Add(“Age”, 18);
x.Add(“From”, DateTime.Today.AddDays(-7));
x.Add(“To”, DateTime.Today);
x.Dump();