C# foreach on a collection of an interface
I'm wondering if there is any functionality built in to C#/LINQ to simplify the following:
foreach(var item in collection)
{
if (item.GetType() == typeof(Type1)
DoType1(item as Type1);
else if (item.GetType() == typeof(Type2))
DoType2(item as Ty开发者_运维知识库pe2);
...
}
to something along the lines of:
collection.ForEachType(Type1 item => DoType1(item), Type2 item => DoType2(item));
I realize that the following is close:
collection.OfType<Type1>.ToList().Foreach(item => DoType1(item));
collection.OfType<Type2>.ToList().Foreach(item => DoType2(item));
But it does not work when the code is dependent on the order of the collection.
The first thing I'd look at is polymorphism; can I instead use a virtual method, and item.DoSomething()
?
The next thing I'd look at would be an enum discriminator, i.e.
switch(item.ItemType) {
case ItemType.Foo: ...
case ItemType.Bar: ...
}
(and add the discriminator to the common interface/base-class)
If the types could be anything, then 4.0 has a trick; if you call te method the same thing for every overload, you can get dynamic
to worry about picking it:
dynamic x = item;
DoSomething(x);
There's nothing built into LINQ, no. I would caution you against using GetType()
like this though - usually it's more appropriate to use is
or as
followed by a null check:
foreach(var item in collection)
{
Type1 itemType1 = item as Type1;
if (itemType1 != null)
{
DoType1(itemType1);
continue;
}
Type2 itemType2 = item as Type1;
if (itemType2 != null)
{
DoType2(itemType1);
continue;
}
// etc
}
That way derived classes will be treated in a way which is usually the appropriate one.
This sort of type testing is generally frowned upon, mind you - it's generally better to put the behaviour into the type itself as a virtual method, and call it polymorphically.
What about something like:
var typeActions = new Dictionary<Type,Action<Object>>();
typeActions.Add(typeof(Type1), obj => DoType1((Type1)obj));
typeActions.Add(typeof(Type2), obj => DoType2((Type2)obj));
collection.Foreach(obj => typeActions[obj.GetType()](obj));
This code is untested (typed directly into the browser).
Your mileage may vary.
Dictionary<Type, Action<object>> typeMap = new Dictionary<Type, Action<object>>();
typeMap[typeof(Type1)] = item => DoType1(item as Type1);
typeMap[typeof(Type2)] = item => DoType2(item as Type2);
var typeToActionQuery =
from item in source
let type = item.GetType()
where typeMap.ContainsKey(type)
select new
{
input = item;
method = typeMap[type]
};
foreach(var x in typeToActionQuery)
{
x.method(x.input);
}
Here's a version of the matching query which considers derived types (Note, an item may be matched to more than 1 type, and therefore handled multiple times).
var typeToActionQuery =
from item in source
from kvp in typeMap
where kvp.Key.IsInstanceOfType(item)
select new
{
input = item;
method = kvp.Value
};
It seems to me that if you just replace "item.GetType() == typeof( Type1 )" with "item is Type1", your foreach loop will be simple enough.
Not by default. Try Reactive Extensions or Elevate
The Reactive Extensions and Elevate both contain a ForEach implementation. Both have quite a few methods that extend the functionality of linq.
You won't find a ForEachType, but ForEach (Rx or Elevate) and OfType<> (Linq) will give you what you want.
精彩评论