views:

340

answers:

7

Given:

interface I
{
}

class B: I
{
}

class C: I
{
}

class A
{

    public void Method(B arg)
    {
    }

    public void Method(C arg)
    {
    }

    public void Method(I arg)
    {
       // THIS is the method I want to simplify.
       if (I is B)
       {
          this.Method(arg as B);
       }
       else if (I is C)
       {
          this.Method(arg as C);
       }
    }
}

I know that there are better ways to design this type of interactions, but because of details which would take too long to explain this is not possible. Since this pattern will be duplicated MANY times, I would like to replace the conditional logic with a generic implementation which I could use just one line. I can't see a simple way to implement this generic method/class, but my instincts tell me it should be possible.

Any help would be appreciated.

A: 

It doesn't exist in a convenient form withing C# - see here for an idea based on F#'s pattern matching, that does exactly what you want. You can do some things with reflection to select the overload at runtime, but that will be very slow, and has severe issues if anything satisfies both overloads. If you had a return value you could use the conditional operator;

return (I is B) ? Method((B)I) : ((I is C) ? Method((C)I) : 0);

Again - not pretty.

Marc Gravell
Sure it does. All you have to do is use the CallByName function. Or if you prefer, use Reflection directly.
Jonathan Allen
That downvote (if you) was IMO unwarranted; CallByName is not a C# language feature (the OP was C#, not VB) - it is a reflection-based runtime feature that is demonstrably slow and brittle, since there is no formal preference sequence.
Marc Gravell
Re the "brittle" - in the general case, you can get real problems when the type matches two overloads - i.e. when in C# you would get error CS0121: The call is ambiguous between the following methods or properties
Marc Gravell
CallByName is just a function that lives in an assembly distributed with the .NET framework. The fact that it has "VisualBasic" in its name doesn't mean C# can't use it.As for being brittle, well so is every other dynamic typing technique.
Jonathan Allen
+16  A: 

I would put the method inside the interface and then let polymorphism decide which method to call

interface I
{
   void Method();
}

class B : I
{
   public void Method() { /* previously A.Method(B) */}
}

class C : I
{
   public void Method() { /* previously A.Method(C) */ }
}

class A
{
   public void Method(I obj)
   { 
     obj.Method();
   }
}

Now when you need to add a new class, you only need to implement I.Method. You don't need to touch A.Method.

jop
That works in some circumstances, but there are times when it isn't appropriate or even possible.
Jonathan Allen
In those cases, why not use the new and virtual modifiers?
casademora
Of course, those scenarios must be listed in here so we can change the code to handle that.
jop
Yes! "Conditional logic based on type" is a red alarm bell that dings "use polymorphism!"
Wedge
@Grauenwolf, unless you give a more specific example then people can't help you provide a solution that works for you. Simply saying "there are times when it isn't appropriate" does not provide enough information for people to help you.
Wedge
What if you didn't create class B or C? Then obviously you can't add an interface to them.
Jonathan Allen
A: 

The only information I have a given points in the is that it matches the interface I, but it must be of type A or B. The prevents simple polymorphism. These classes are part of much large system and A and B have their own responsibilities (thus should not have other methods added to make my life simple), but many other classes use classes A and B as input data.

class A
{

   public void Method(I obj)
   {  
       obj.Method();

   } 

}

will not work because I can't change (and it does not make sense) to add Method to the interface and thus A and B.

I already know of the methods already outline, but because of much larger system designs I can't use these simple methods.

Jim Kramer
Please put this in your question instead.
jop
and please clarify which classes you can't change. Can you change I, B or C? Is A the only class you can change?
jop
+1  A: 

This is kinda ugly but it gets the job done:

public void Method(B arg)
{
  if (arg == null) return;
...
}
public void Method(C arg)
{
  if (arg == null) return;
...
}

public void Method(I arg)
{
  this.Method(arg as B);
  this.Method(arg as C);
}

I don't think I would do it this way, though. It actually hurts looking at that. I'm sorry I forced you all to look at this as well.

Jeffrey L Whitledge
Not the way I would do it, but an interesting technique anyways.
Jonathan Allen
A: 

Easy. In Visual Basic I do this all the time using CallByName.

Sub MethodBase(value as Object)
    CallByName(Me, "RealMethod", CallType.Method, value)

This will call the overload of RealMethod that most closely matches the runtime type of value.

I'm sure you can use CallByName from C# by importing Microsoft.VisualBasic.Interaction or by creating your own version using reflection.

Jonathan Allen
See my response to your comment why this isn't a good idea (besides the fact that it is slow and CAS might not allow it...)
Marc Gravell
+4  A: 

What you want is double dispatch, and visitor pattern in particular.

Ilya Ryzhenkov
+1  A: 
interface I
{ 
} 

class B : I
{
}

class C : I
{
}    

class A 
{
    public void Method(B arg)
    {
     Console.WriteLine("I'm in B");
    }

    public void Method(C arg)
    {
     Console.WriteLine("I'm in C");
    }

    public void Method(I arg)
    {
     Type type = arg.GetType();

     MethodInfo method = typeof(A).GetMethod("Method", new Type[] { type });
     method.Invoke(this, new I[] { arg });
    }
}
Vivek
While I accept this as a very good and simple solution. The answer below about the visitor pattern is also a very good.I finally found what I think is the best answer uses a separate dispatcher in the following linkhttp://www.codeproject.com/KB/architecture/DynamicActionDispatcher.aspx
Jim Kramer