How can I enumerate through an object of type `T` when it is a IEnumerable
This is a bit difficult to explain. So here it goes.
I have a function like this:
public T FooBar<T>(Func<T> function)
{
T returnData = function();
// want to iterate through returnData to do something to it
return returnData;
}
If the returnData
(T) is an IEnumerable
list, then I would like to enum开发者_如何学Goerate through returnData
to modify its contents using reflection. But I can't seem to be able to do it. When I try to cast returnData
to an enumerable type, I get an exception:
Unable to cast object of type
'System.Collections.Generic.List`1[Cars]'
to type
'System.Collections.Generic.List`1[System.Object]'.
I will not know that the return type will be a list of 'cars' for example ahead of time, only at run time. So I have to check using reflection if it is a list, and then try to cast it so that I can enumerate through it.
Unless I am going about it the wrong way. How can I enumerate through returnData
if it is of type T
?
One approach is to add a type constraint on T
, but this is not ideal:
public T FooBar<T>(Func<T> function) where T : IEnumerable
{
// T is not strongly typed for the enumerated item
If you changed your method slightly (w.r.t. T
):
public IEnumerable<T> FooBar<T>(Func<IEnumerable<T>> function)
Then you have strong typing on the actual item being enumerated with the added bonus of accepting enumerable objects.
So I noticed from a second read of your question, there is some confusion about what T
means for your variable returnData
. In the case where FooBar()
is passed a List<Car>
, T
is List<Car>
, and really has no association with the generic type specification of the List<>
itself. You can think of it as some List<U>
where U
is some other, unknown type.
At runtime you will have no simple way to get to U
as it is hidden, so to speak, inside T
. You could use overloading as some of the other answerers recommend, and provide a non-IEnumerable<U>
method and one which takes arguments of type Func<IEnumerable<T>>
.
Perhaps with some more details about the goal of FooBar<T>
we could make some more specific recommendations.
if (returnData is System.Collections.IEnumerable)
{
foreach (object o in (System.Collections.IEnumerable)returnData)
{
// Do something.
}
}
Really, though, why not have an additional overload like this:
public T FooBar<T>(Func<IEnumerable<T>> function)
Have you tried type casting to IEnumerable
instead of IEnumerable<T>
? With IEnumerable
you can still use it in a foreach
loop. The variable each item would go in should be of type object
i.e.:
foreach(object item in (IEnumerable)T){...}
You should check first to be sure that T implements IEnumerable
.
The issue here is IEnumerable and IEnumerable Of T are not the same... but you can check for the difference and account for it in your code. Note that IEnumerable Of T inherits IEnumerable, so you can wrap the check for the generic version inside the non-generic version.
The following worked for me in a small test I wrote - I hope it is sufficient for you to do what you need.
Here is the meat and potatoes:
class FooBarOfT
{
public T FooBar<T>(Func<T> function)
{
T returnData = function();
//Want to iterate through returnData to do something to it.
if (returnData is IEnumerable)
{
// get generic type argument
var returnDataType = returnData.GetType();
if (returnDataType.IsGenericType)
{
// this is a System.Collections.Generic.IEnumerable<T> -- get the generic type argument to loop through it
Type genericArgument = returnDataType.GetGenericArguments()[0];
var genericEnumerator =
typeof(System.Collections.Generic.IEnumerable<>)
.MakeGenericType(genericArgument)
.GetMethod("GetEnumerator")
.Invoke(returnData, null);
IEnumerator enm = genericEnumerator as IEnumerator;
while (enm.MoveNext())
{
var item = enm.Current;
Console.WriteLine(string.Format("Type : {0}", item.GetType().Name));
}
}
else
{
// this is an System.Collections.IEnumerable (not generic)
foreach (var obj in (returnData as IEnumerable))
{
// do something with your object
}
}
}
return returnData;
}
}
I also set up some supporting test classes:
class Foo
{
private string _fooText;
public Foo(string fooText)
{
_fooText = fooText;
}
public string Execute()
{
return string.Format("executed! with {0} !", _fooText);
}
}
class Bar
{
public string BarContent { get; set; }
}
And a small console app to run some tests:
class Program
{
static void Main(string[] args)
{
// tests
Func<string> stringFunc = () =>
"hello!";
Func<List<Foo>> listFooFunc = () =>
new List<Foo>
{
new Foo("Hello!"),
new Foo("World!")
};
Func<IEnumerable> ienumerableFooFunc = () =>
new Hashtable
{
{ "ItemOne", "Foo" },
{ "ItemTwo", "Bar" }
};
var fooBarOfT = new FooBarOfT();
fooBarOfT.FooBar(stringFunc);
fooBarOfT.FooBar(listFooFunc);
fooBarOfT.FooBar(ienumerableFooFunc);
Console.ReadKey();
}
}
精彩评论