Alternatywa dla Convert.ChangeType()

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ć.

Opublikowane 09 lipca 09 06:43 przez Procent
Filed under: ,

Komentarze:

# dotnetomaniak.pl said on lipca 9, 2009 06:52:

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

# nandrew said on lipca 17, 2009 08:27:

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)

# Procent said on lipca 17, 2009 13:44:

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.

# nandrew said on lipca 17, 2009 13:54:

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");

# Procent said on lipca 20, 2009 06:41:

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ć.

Komentarze anonimowe wyłączone

About Procent