Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List filtering using multiple classes/interfaces

Basically I have a list of objects where each object might implement a different set of interfaces:

List<BaseObject> objects;

class BaseObject
{
  public void DoStuff();
}

interface IX
{
  void DoX();
}

interface IY
{
  void DoY();
}

interface IZ
{
  void DoZ();
}

And i would like to write something like this:

foreach(var obj in objects.OfType<BaseObject and IX>)
{
  obj.DoStuff();
  obj.DoX();
}

(E.g. i perform a specific algorithm for objects of type BaseObject and IX without having to do typecasts there)

Is it possible to do in C#? What's the most elegant solution?

I can do this:

foreach(var obj in objects.OfType<IX>)
{
  var baseobj = (BaseObject)obj.DoStuff();
  obj.DoX();
}

But i find it ugly.

And I might need to apply specific operations to types that implement say interface IX and interface IZ.

foreach(var obj in objects.OfType<BaseType and IX and IZ>)
{
  obj.DoStuff();
  obj.DoX();
  obj.DoZ();
}
like image 380
JBeurer Avatar asked Nov 23 '11 23:11

JBeurer


4 Answers

One possible way is to use dynamic:

foreach(var xz in objects.Where(o => o is IX && o is IZ).Select(o => (dynamic)o))
{
    xz.DoStuff();
    xz.DoX();
    xz.DoZ();   
}

Of course, you can still cast if you don't want to use dynamic:

foreach(var xz in objects.Where(o => o is IX && o is IZ))
{
    xz.DoStuff();
    ((IX)xz).DoX();
    ((IZ)xz).DoZ(); 
}
like image 164
Jeff Ogata Avatar answered Oct 19 '22 20:10

Jeff Ogata


It's not possible to do EXACTLY what you want.

Here's what you CAN do.

First, you want to only operate on types that implement IX, IY, AND IZ? Then just chain together your .OfType methods. OfType will filter the list for you:

foreach (var obj in objects.OfType<IX>().OfType<IY>().OfType<IZ>()) {

Unfortunately, obj will only be strongly-typed IZ, because there's no such thing as a type that implements all 3 interfaces. So you still have to cast, but you're guaranteed that the cast will work:

foreach (var obj in objects.OfType<IX>().OfType<IY>().OfType<IZ>()) {
    ((IX)obj).DoX();
    ((IY)obj).DoY();
    ((IZ)obj).DoZ(); // Note, You could also just do obj.DoZ(), but consistency looks better.
}
like image 30
Scott Rippey Avatar answered Oct 19 '22 19:10

Scott Rippey


The most concise way to avoid a lot of casting and type testing is to introduce an extension method that handles it for you.

Try this:

public static void DoAs<T>(this object @this, Action<T> action)
    where T : class
{
    var t = @this as T;
    if (t != null)
    {
        var a = action;
        if (a != null)
        {
            a(t);
        }
    }
}

Now, I assume in your last example loop that you only want to execute the loop if the object implements both IX & IZ. So you would then write:

foreach(var obj in objects)
{
    obj.DoAs<IX>(x =>
        x.DoAs<IZ>(z =>
        {
            obj.DoStuff();
            x.DoX();
            z.DoZ();
        }));
}

Your first/middle loop doesn't need any new extension method as I think this should be simple enough:

foreach(var obj in objects.OfType<IX>())
{
    (obj as BaseObject).DoStuff();
    obj.DoX();
}

I hope this helps.

like image 2
Enigmativity Avatar answered Oct 19 '22 19:10

Enigmativity


With these classes and interfaces...

List<BaseObject> objects; 

class BaseObject 
{ 
  public void DoStuff(); 
} 

interface IX 
{ 
  public void DoX(); 
} 

interface IY 
{ 
  public void DoY(); 
} 

interface IZ 
{ 
  public void DoZ(); 
} 

class X : BaseObject, IX { }
class Y : BaseOjbect, IY { }
class Z : BaseObject, IZ {  }
class XY : BaseObject, IX, IY { }

Asumming you populate ojbect with any Class defined above

foreach (var o in objects)
{
  o.DoStuff();
  if (o is IX)
    ((IX)o).DoX(); //executed on class X and XY
  if (o is IY)
    ((IY)o).DoY(); //excuted on class Y and XY
  if (o is IZ)
    ((IZ)o).DoZ(); //excuted on class Z
}

That should do what you are looking for.

UPDATED per your comment.

foreach (var o in objects)
{
  o.DoStuff();
  if (o is IX and o is IY)
  {
    // do something different only for XY
  }
  else if (o is IX)
    ((IX)o).DoX(); //executed on class X 
  else if (o is IY)
    ((IY)o).DoY(); //excuted on class Y 
  else if (o is IZ)
    ((IZ)o).DoZ(); //excuted on class Z
}

UPDATED No type casting.

Requires another interface. (Additionally, DoStuff() should be on all IX, IY, IZ).

interface IXY : IX, IY { }

//       This IXY could be var, but just strongly typing it for example
foreach (IXY o in objects.OfType(IXY)
                         .Cast<IXY>())
{
  o.DoStuff();
  o.DoX();
  o.DoY();
}

You could also do someting CRAZY like:

foreach (var obj in objects.OfType<IX>()
                           .OfType<IY>()
                           .OfType<IZ>()
                           .Select(o => new 
                                   {
                                      AsIX = (IX)o,
                                      AsIY = (IY)o,
                                      AsIZ = (IZ)o
                                   }) 
{  
    obj.AsIX.DoX();  
    obj.AsIY.DoY();  
    obj.AsIZ.DoZ();
} 
like image 2
Erik Philips Avatar answered Oct 19 '22 19:10

Erik Philips