1

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.

Jason Fry
  • 1,204
  • 12
  • 24
  • When you say that the code is dependent on the order, is that to say that you know at compile time that item 1 for instance is always a string, and item 2 is always a double? – DJ Quimby Sep 29 '11 at 17:58
  • @DJ Quimby +1. Missed that. Curious. – Ritch Melton Sep 29 '11 at 18:00
  • 1
    Kind of sounds like DoType1 and DoType2 should be abstracted into a "Do" method on type1 and type2. So you could just do a foreach and call the do on each. – keithwill Sep 29 '11 at 18:01
  • @RitchMelton Agreed. If the order is already known, the foreach loop might be unnecessary. – DJ Quimby Sep 29 '11 at 18:02
  • 1
    Why are you using Reflection to check the exact type and then just turning right around and using runtime type checking with the "as" operator? Wouldn't it be a lot more clear to say "if (item is T1) DoT1(item as T1)", or "T1 t1 = item as T1; if (t1 != null) DoT1(t1);" ? – Eric Lippert Sep 29 '11 at 18:13
  • Also, I suspect that you are doing this runtime type check in an effort to simulate either single or double virtual dispatch. If you need to take an action based on the runtime type of an object, consider making the method a virtual method on the object's base class. If you need to take an action based on the runtime type of *two different objects* then consider using the visitor pattern to implement double virtual dispatch. – Eric Lippert Sep 29 '11 at 18:15
  • Because I have never used the "is" operator before - I will be sure to in the future, however. The order is not known at compile time. – Jason Fry Sep 29 '11 at 18:15
  • @user961969 - Not knowing about "is" makes me think that reading Bill Wagner's books or John Skeet's might make for a good weekend worth of reading. – Ritch Melton Sep 29 '11 at 18:46
  • @RitchMelton - Agreed - I just ordered his book. – Jason Fry Sep 29 '11 at 18:55
  • @user961969 - Which one? – Ritch Melton Sep 29 '11 at 18:56
  • @RitchMelton C# in Depth – Jason Fry Sep 29 '11 at 18:58

6 Answers6

6

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);
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
5

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

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).

MarkPflug
  • 28,292
  • 8
  • 46
  • 54
1

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
  };
Amy B
  • 108,202
  • 21
  • 135
  • 185
0

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.

Ritch Melton
  • 11,498
  • 4
  • 41
  • 54
0

It seems to me that if you just replace "item.GetType() == typeof( Type1 )" with "item is Type1", your foreach loop will be simple enough.

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172