Swojego czasu Procent zwrócił uwagę na możliwość dokonywania konwersji pomiędzy typami za pomocą TypeConverter, który znajduje się w BCL.
W jednym z małych podprojektów musiałem dokonać konwersji parametrów przekazywanych jako lista wartości określonego typu rozdzielona przecinkami. Dokładnie mówiąc ktoś mógł wpisać w command line:
program.exe -o -action add -values ola,ma,kota,a,kto,ma,ole
Dzięki konstrukcji Command Line Parser wiedziałem, że o jest poleceniem, nazwa klasy implementująca to polecenie to OCommand. Klasa ta posiada takie własności jak Add i Values. Add jest enumeratorem zaś Values jest generyczną listą stringów. Skoro już coś wiemy, to teraz trzeba było znaleźć rozwiązanie problemu – jak przekonwertować wartości aby stały się generyczną listą stringów?
Dodatkowo chciałem mieć pewność, iż każdy parametr kolekcji przekazanej jest danego typu – czyli należało sprawdzić każdą wartość przekazaną w atrybucie values czy jest typu string. Żeby jednak nie robić czegoś hardcoded, chciałem to zrobić tak by było to uniwersalne – przekazanie wartości int, datetime itp. itd.
Pierwszym krokiem było dostanie się do PropertyDescriptor danej własności:
var property = ( from prop in properties where string.Compare(prop.Name, dumpKeyValue.Key, true) == 0 || prop.GetAttribute<ParameterAttribute>().ShortName == dumpKeyValue.Key || prop.Name == "CommandName" select prop ).FirstOrDefault();
Kiedy już miałem property to mogłem działać dalej:
object valueForProp; switch (property.PropertyType.Name) { case "List`1": case "IList`1": MethodInfo methodInfo = typeof(StringEx).GetMethod("GetListFromCommaSeparatedValues", BindingFlags.Static | BindingFlags.NonPublic); Type[] genericArguments = property.PropertyType.GetGenericArguments(); MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments); valueForProp = genericMethodInfo.Invoke(null, new object[] { keyValuePair.Value }); break; // other cases default: // ... break; } // valueForProp check property.SetValue(currentCommand, valueForProp);
Dzięki prostemu switch jestem wstanie wykryć, kiedy używam generycznej listy, następnie za pomocą typu własności wyciągnąć informacje na temat generycznego typu listy i stworzyć generyczną definicję metody, którą potem można wywołać.
Sama metoda GetListFromCommaSeparatedValues jest Extension Method na wartości string:
public static List<T> GetListFromCommaSeparatedValues<T>(this string commaSeparatedValues) { var list = new List<T>(); // pobieramy konwerter dla danego typu TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(T)); // sprawdzamy czy można wykonać konwersję za pomocą wartości string if (!typeConverter.CanConvertFrom(typeof(string))) { return list; } // każdą wartość rodzieloną przecinkiem dodaj do listy wynikowej foreach (var value in commaSeparatedValues.Split(',')) { list.Add((T)typeConverter.ConvertFromInvariantString(value.Trim())); } return list; }
Prosty zabieg a dzięki niemu mogę podawać wartości w dowolnym typie, który może zostać przekonwertowany z wartości stringowej.