devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
3 minut

Alternatywa dla Convert.ChangeType()


09.07.2009

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

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Notify of
nandrew
nandrew

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

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
nandrew

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

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

Moja książka

Facebook

Zobacz również