简体   繁体   中英

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 Type2);
    ...
}

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, ie

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
  };

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.

在我看来,如果仅将“ item.GetType()== typeof(Type1)”替换为“ item is Type1”,那么您的foreach循环就足够简单了。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM