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.