Alternatywa dla Convert.ChangeType()

4

Drugi raz w ciągu kilku dni przytrafiły mi się kłopoty podczas wykorzystania metody Convert.ChangeType(). Scenariusz jest bardzo prostu: mam wartość pobraną skądś-tam (baza danych, http request czy cokolwiek innego) reprezentującą znany mi typ, jednak przechowywaną w postaci stringa. Wszystko śmigało jak trzeba dopóki traktowałem w ten sposób zwykłe liczby i daty. Jakiś czas temu wpadł mi tam Guid, co skończyło się wyjątkiem InvalidCastException. Teraz z kolei to samo przytrafiło się dla TimeSpan. To samo zresztą tyczy się "typów nullowalnych".

Wystarczyło zajrzeć Reflectorem w trzewia .NETa, aby poznać tego przyczynę. Wspomniana metoda potrafi sobie poradzić tylko z kilkoma na sztywno zdefiniowanymi typami:

Kilka kliknięć dzieli nas od podejrzenia jakie to są typy; inicjalizacja tablicy je zawierającej odbywa się w statycznym konstruktorze klasy Convert:

Cóż nam pozostaje? Najpierw pomyślałem, że jedyne wyjście to rozszerzenie tej metody i stworzenie takiego oto monstera oraz rozwijanie go w razie wystąpienia kolejnych nieprzewidzianych typów:

  1:  public static object ConvertEx(object value, Type conversionType)
  2:  {
  3:  	string str = value.ToString();
  4:  
  5:  	if (conversionType == typeof(Guid))
  6:  		return new Guid(str);
  7:  	if (conversionType == typeof (TimeSpan))
  8:  		return TimeSpan.Parse(str);
  9:  
 10:  	return Convert.ChangeType(value, conversionType);
 11:  }

Chwilę potem jednak dotarło do mnie, że przecież te typy JAKOŚ muszą być dynamicznie wewnątrz .NET tworzone z napisowej reprezentacji. Chociażby zwykłe ustawienia aplikacji w pliku .config mogą mieć takie typy i klasy odpowiedzialne za parsowanie konfiguracji radzą sobie z nimi doskonale. Strzał Reflectorem w klasę ConfigurationElement, która przecież ma tą funkcjonalność, okazał się dobrym początkiem poszukiwań. Oszczędzę omawiania całej drogi, w tym przypadku ważny jest sam rezultat. Rozwiązaniem okazała się klasa TypeDescriptor, z wykorzystaniem której działające rozwiązanie wygląda tak (działa dla TimeSpan, dla Guidów, dla typów nullowalnych…):

  1:  TimeSpan result = (TimeSpan)TypeDescriptor.GetConverter(typeof (TimeSpan)).ConvertFromInvariantString(str);

Jeden problem z głowy. Oczywiście od razu przychodzi na myśl napisanie fajnej metody generycznej, ale to już każdy może sobie sam zaimplementować.

Share.

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

4 Comments

  1. Hmm, skoro znasz typ docelowy i podajesz do na poziomie kodu (a nie odgadujesz dynamicznie) to nie prościej, szybciej, czytelniej i bardziej elegancko byłoby napisać:
    “TimeSpan res = TimeSpan.Parse(str);”
    (większość wbudowanych typów “prostych” ma statyczną metodę Parse)

  2. Jeżeli znam typ docelowy to oczywiście że użyję [Try]Parse(). W tym przypadku na przykładzie TimeSpan demonstrowałem jedynie, że prezentowane przeze mnie rozwiązanie działa również dla niego (w przeciwieństwie do Convert.ChangeType()). Docelowe wykorzystanie to tak jak napisałeś – dynamiczna konwersja na nieznany podczas kompilacji typ.

  3. Jeżeli tak, to co miałeś na myśli przez “napisanie fajnej metody generycznej”? Takie metody nie działają dynamicznie, tz. nie można napisać:
    string s = ConvertEx<obj.GetType()> (“s”);

  4. Metoda generyczna przydałaby się na przykład w klasie odpowiedzialnej za dostarczanie aplkacji konfiguracji. Wówczas wygodniej byłoby korzystać z mechanizmu:
    T Get<T>(string key);
    var value = Get<TimeSpan>(“SomeInterval”);
    niż każdorazowo pisać metody charakterystyczne dla typu który chcemu uzyskać:
    object Get(string key);
    var value = TimeSpan.Parse(Get(“SomeInterval”));
    Moim zdaniem pierwszy przykład jest dużo czytelniejszy i, co dość istotne, wygląda tak samo dla każdego typu. Jednocześnie obsługę stostownych wyjątków umieszczamy tylko w jednym miejscu – metodzie Get<T> – zamiast zajmować się nią każdorazowo w zależności od tego jaki typ chcemy uzyskać.

Newsletter: devstyle weekly!
Dołącz do 1000 programistów!
  Zero spamu. Tylko ciekawe treści.
Dzięki za zaufanie!
Do przeczytania w najbliższy piątek!
Niech DEV będzie z Tobą!