Mapowanie typów

20 Lipca 2016

Spotkałem się ostatnio z następującym problemem: Mam bibliotekę z modelami, oraz drugą opisującą Dto (Data Transfer Object), w której typy danych są bardzo zbliżone do modeli, ale są to mimo wszystko inne typy. Szukając rozwiązania, trafiłem na Mapster, bibliotekę do mapowania typów.

Użycie Mapstera jest dość proste:

using Mapster;
...

	var result = TypeAdapter.Adapt<NewType>(original);

I w większości przypadków to w zupełności wystarcza. Ale są też bardziej złożone przypadki.

IEnumerable

Kiedy pod interfejsem IEnumerable siedzi nie kolekcja, a iterator albo wyrażenie LINQ, to można dostać po twarzy wyjątkiem

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
	cannot invoke non-delegate type

Rozwiązanie jest dość proste. Trzeba użyć .ToList() lub .ToArray(), aby przekształcić IEnumerable na prostą kolekcję.

Generyki

Niech się przypadkiem stanie, że nasz model to

class Model
{
	public object Value { get; set; }
}

a typ na który go mapujemy to

class Model<T>
{
	public T Value { get; set; }
}

Teraz nie jesteśmy w stanie uruchmić TypeAdapter.Adapt<>, bo nie znamy T w czasie kompilacji. Trzeba się wtedy odwołać do Refleksji.

typeof(TypeAdapter).GetMethod("Adapt", new[] { typeof(object) })	//get Adapt<>(object)
	.MakeGenericMethod(
		typeof(Model<>).MakeGenericType(
			model.Value.GetType()))
	.Invoke(null, model);	//returns Model<T>, with the right type
//Note: Invoke gets null, because we invoke a static method, otherwise it'd be an object instance

Nie wygląda to bardzo pięknie, a gdy mamy np. IEnumerable<Model<T>> to jest jeszcze więcej zabawy, ale działa.