2

I have two types: let's call them A and B. A can be converted to B using an adapter method.

I then have a collection of A's in a List<A> (it could be any collection type that supports IEnumerable<A>).

I now want to convert from IEnumerable<A> to IEnumerable<B>. I know the Type of each of A and B, and I have a method to convert an A into a B, but my method and/or class is not templated itself, so I do not have access to the template type; e.g. the T in IEnumerable<T>.

I effectively want to write this ConvertCollection method, where I know "from" is of type IEnumerable<{something}>:

object ConvertCollection(object from, Type fromType, Type toType, Converter converter);

My converter looks like this:

delegate object Converter(object from);

My attempt leaves me here:

object ConvertCollection(object from, Type fromType, Type toType, Converter converter)
{
    return ((IEnumerable<object>)from).Select(converter);
}

which partly works. If I call it like this

ConvertCollection(new List<A>() { new A() }, typeof(A), typeof(B), AToBConverter);

the returned collection does contain a collection of Bs, but the collection itself is of type IEnumerable<object>, not IEnumerable<B>, because I don't know how to cast to IEnumerable<{toType}>. (It matters because the result needs to be serialized).

I can attack it from the other end and create the correct return type like this:

var result = Activator.CreateInstance(typeof(List<>).MakeGenericType(toType));
// TODO: populate result here
return result;

but then the problem is that to achieve the TODO part, I need to call List<> methods on result, but I can't cast it to any type of List<> because of Co/ContraVariance rules, so even though I know the type supports List<> methods, I can't get at them to use them to populate the list; e.g. to use Add().

Is there a way to do this without using 'dynamic' and without too much reflection? I know I could locate and invoke the Add() method via reflection, but it seems like it shouldn't be necessary.

.NET 4.0 BTW

-- Clarification

As Euphoric correctly speculates, and I tried but rather badly failed to convey above, I know the types A and B at runtime, but I do not know them at compile time. Hence the direct use of generics is not an option. I do know that the collections (both supplied and as must be returned) implement the generic IEnumerable<>. That is all fixed and outside my control. (I've adjusted the title accordingly).

** Edit 2: fixed some formatting causing <> to not display (easy to accidentally omit the back-ticks!)

sellotape
  • 8,034
  • 2
  • 26
  • 30

3 Answers3

3

Using the LINQ Select method:

var result = listA.Select(a => Converter(a));

Since you are using .NET 4.0, you really should avoid using object and use generics.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
  • This is basically as I had it above, is it not? The problem is that because the return type of Converter is object, result becomes a collection of objects, rather than a collection of B's, even though it contains B's. – sellotape Jul 12 '13 at 09:45
2

The solution I settled on was to use reflection to invoke the Enumerable.Cast<> method to cast the resultant collection from IEnumerable<object> to the correct IEnumerable<> type. I got the idea from the answer to this question: Convert IEnumerable to IEnumerable<T> when T isn't known until runtime. Seems to involve very little performance penalty.

So the full answer becomes:

object ConvertCollection(object from, Type fromType, Type toType, Converter converter)
{
    var partialResult = ((IEnumerable<object>)from).Select(converter);
    var castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(toType);
    return castMethod.Invoke(null, new[] { partialResult });
}
Community
  • 1
  • 1
sellotape
  • 8,034
  • 2
  • 26
  • 30
  • Thank you for sharing your solution. I combined it with a subsequent similar call to `ToList` and it works miracles ;-) – t3chb0t Jun 19 '15 at 21:18
0

Maybe something like this?

IEnumerable<TTo> ConvertCollection<TFrom,TTo>(object from, Converter converter)
{
    return ((IEnumerable<TFrom>)from).Select(a=>(TTo)converter(a)).ToList();
}

Then you simply call it:

ConvertCollection<A,B>(new List<A>() { new A() }, AToBConverter);
Euphoric
  • 12,645
  • 1
  • 30
  • 44
  • That would work if I knew the types at compile time, but as you suggested elsewhere, I don't. Apologies for the initial vagueness; my bad. – sellotape Jul 12 '13 at 09:46