Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cleanly deal with different behavior based on polymorphism

Suppose I have an interface IFoo with implementation classes VideoFoo, AudioFoo, and TextFoo. Suppose further that I cannot modify any of that code. Suppose that I would then like to write a function that acts differently based on the runtime type of IFoo, such as

Public Class Bar
    Public Shared Sub Fix(ByVal Foo as IFoo)
        If TypeOf Foo Is VideoFoo Then DoBar1()
        If TypeOf Foo Is AudioFoo Then DoBar2()
        If TypeOf Foo Is TextFoo Then DoBar3()

    End Sub
End Class

I would like to refactor this to use overloaded methods:

Sub DoBar(ByVal foo as VideoFoo)
Sub DoBar(ByVal foo as AudioFoo)
Sub DoBar(ByVal foo as TextFoo)

But the only way I see to do something like that would be to write

Sub DoBar(ByVal foo as IFoo)

Then I have to do my "If TypeOf ... Is" again. How can I refactor this to take advantage of the polymorphism of the implementations of IFoo without manually checking the types?

(in VB.NET, though my question applies to C# too)

like image 715
Patrick Szalapski Avatar asked Mar 24 '11 20:03

Patrick Szalapski


2 Answers

What you're asking about is Multiple Dispatch, or a language feature that allows method overload resolution at runtime instead of compile time.

Unfortunately C# and VB.NET are both single-dispatch languages, which means the method overload is chosen at compile time. This means that the overload for an IFoo object will always be chosen for IFoo, regardless of its implementing type.

There are ways around this however. One way is to use the Visitor design pattern to implement double-dispatch, which would work. In C# you can also use the new dynamic keyword to force the run-time environment to resolve the overload at run-time. I wrote a blog entry about how to perform collision handling using this technique, but it's certainly applicable to what you're doing too.

I'm not terribly familiar with VB.NET, but I believe the language exhibits some dynamic behaviours by default, if the objects are cast to Object. Someone please correct me if this is wrong.

like image 186
Martin Doms Avatar answered Sep 22 '22 17:09

Martin Doms


Well, one option is to simply overload the Fix() method so that you have one overload for each type implementing IFoo. But I suspect you want to accept the interface directly, rather than it's implementing types.

What you're actually looking for is multiple dispatch. Normally, C#/VB use the types of the argument(s) to perform overload resolution at compile time, and dynamic dispatch of the call based on the runtime type of the instance on which the method is called. What you want is to perform overload resolution at runtime based on the runtime types of the arguments - a feature that neither VB.NET or C# directly support.

In the past, I've generally solved this kind of problem using a dictionary of delegates indexed by System.Type:

private readonly Dictionary<Type,Action<IFoo>> _dispatchDictionary;

static Bar()
{
    _dispatchDictionary.Add( typeof(TextFoo),  DoBarTextFoo );
    _dispatchDictionary.Add( typeof(AudioFoo), DoBarAudioFoo );
    _dispatchDictionary.Add( typeof(VideoFoo), DoBarVideoFoo );        
}

public void Fix( IFoo foo )
{
   Action<IFoo> barAction;
   if( _dispatchDictionary.TryGetValue( foo.GetType(), out barAction ) )
   {
      barAction( foo );
   }
   throw new NotSupportedException("No Bar exists for type" + foo.GetType());
}

private void DoBarTextFoo( IFoo foo ) { TextFoo textFoo = (TextFoo)foo; ... }
private void DoBarAudioFoo( IFoo foo ) { AudioFoo textFoo = (AudioFoo)foo; ... }
private void DoBarVideoFoo( IFoo foo ) { VideoFoo textFoo = (VideoFoo)foo; ... }

However, as of C# 4, we can now use the dynamic keyword in C# do essentially do the same thing (VB.NET does not have this feature as of yet):

public void Fix( IFoo foo )
{
    dynamic dynFoo = foo;
    dynamic thisBar = this;

    thisBar.DoBar( dynFoo ); // performs runtime resolution, may throw
}

private void Dobar( TextFoo foo ) { ... /* no casts needed here */ }
private void Dobar( AudioFoo foo ) { ... }
private void Dobar( VideoFoo foo ) { ... }

Note that using the dynamic keyword this way has a price - it requires that the call site be processed at runtime. It essentially spins up a version of the C# compiler at runtime, processes the metadata captured by the compiler, performs runtime analysis of the types, and spits out C# code. Fortunately, the the DLR can typicall cache such call sites effectively after their first use.

As a general rule, I find both of these pattern to be confusing, and overkill for most situations. If the number of subtypes is small and they are all known ahead of time, a simple if/else block can bemuch simpler and clearer.

like image 44
LBushkin Avatar answered Sep 21 '22 17:09

LBushkin