2

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.

jop
  • 82,837
  • 10
  • 55
  • 52
Jim Kramer
  • 123
  • 10

6 Answers6

16

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
  • 82,837
  • 10
  • 55
  • 52
  • That works in some circumstances, but there are times when it isn't appropriate or even possible. – Jonathan Allen Oct 06 '08 at 05:31
  • In those cases, why not use the new and virtual modifiers? – casademora Oct 06 '08 at 05:44
  • Of course, those scenarios must be listed in here so we can change the code to handle that. – jop Oct 06 '08 at 07:13
  • Yes! "Conditional logic based on type" is a red alarm bell that dings "use polymorphism!" – Wedge Oct 06 '08 at 08:22
  • @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 Oct 06 '08 at 08:24
  • What if you didn't create class B or C? Then obviously you can't add an interface to them. – Jonathan Allen Oct 06 '08 at 23:23
5

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

Ilya Ryzhenkov
  • 11,782
  • 1
  • 40
  • 50
1

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
  • 58,241
  • 9
  • 71
  • 99
1
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
  • 16,360
  • 5
  • 30
  • 37
  • 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 link http://www.codeproject.com/KB/architecture/DynamicActionDispatcher.aspx – Jim Kramer Oct 08 '08 at 03:02
0

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.

Community
  • 1
  • 1
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Sure it does. All you have to do is use the CallByName function. Or if you prefer, use Reflection directly. – Jonathan Allen Oct 06 '08 at 05:32
  • 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 Oct 06 '08 at 06:56
  • 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 Oct 06 '08 at 07:01
  • 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 Oct 06 '08 at 23:26
0

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
  • 68,373
  • 70
  • 259
  • 447
  • 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 Oct 06 '08 at 07:04