From Wikipedia:

In computer science, dynamic dispatch is the process of selecting which implementation of a polymorphic operation (method or function) to call at runtime.

There are many ways to acheive dynamic dispatch in C#. I will cover a few ways to acheive single dispatch based on the argument into an overloaded method.

Say you have a class called ObjectProcessors that has a overloaded methods named Process(xxx value) that take a single argument like so:

public class ObjectProcessors  
{
  public void Process(object value)
  {
    LogMessage("object");
  }

  public void Process(string value)
  {
    LogMessage("string");
  }

  public void Process(MyClass value)
  {
    LogMessage("MyClass");
  }

  public void ProcessUnknownType()
  {
    LogMessage("NULL");
  }

  //More overloads as necessary

  private void LogMessage(string typeName)
  {
    Console.WriteLine("Processed a value of type {0}.", typeName);
  }
}

For all the below examples, you have a list of objects of varying types:

var objects = new List<object>  
{
  "A string",    
  new object(),
  new MyClass(),
  null, //null has no type
  new NonOverloadedClass() //No overload specified
}

Direct Type Matching

To acheive dynamic dispatch you could just match on the type:

public void Dispatch(List<objects> objects)  
{
  var processors = new ObjectProcessors();

  foreach (var obj in objects)
  {
    if(obj == null)
    {
      processors.ProcessUnknownType();
      continue;
    }

    var objType = obj.GetType();

    if(objType == typeof(string))
    {
      processors.Process((string)obj);
      continue;
    }

    if(objType == typeof(MyClass))
    {
      processors.Process((MyClass)obj);
    }

    processors.Process(obj);
  }
}

Advantages

  • The fastest method described here (by a wide margin).
  • No magic string for the overloaded method.

Disadvantages

  • Lots of brittle code.
  • You have to add a new conditional for EVERY overload.

Reflection

You could also use reflection:

public void Dispatch(List<objects> objects)  
{
  var processors = new ObjectProcessors();
  var processorsType = processors.GetType();

  foreach (var obj in objects)
  {
    if (obj == null)
    {
      processors.ProcessUnknownType();
    }
    else
    {
      var mi = processorsType.GetMethod("Process", new[] {obj.GetType()});
      mi.Invoke(processors, new[] {obj});
    }
  }
}

Advantages

  • No additional code needed for new overloads.
  • Much less code than direct type matching.

Disadvantages

  • Slowest method shown here.
  • Magic string name for process method.

ImpromptuInterface

Next, you could use ImpromptuInterface in a similar fashion:

public void Dispatch(List<objects> objects)  
{
  var processors = new ObjectProcessors();
  var ci = new CacheableInvocation(InvocationKind.InvokeMemberAction, "Process", 1);

  foreach (var obj in objects)
  {
      if (obj == null)
      {
          processors.ProcessUnknownType();
      }
      else
      {
          ci.Invoke(processors, obj);
      }
  }
}

Advantages

  • Very little code and no additional code needed for new overloads.
  • Almost twice as fast as reflection.

Disadvantages

  • Direct type matching is still almost 10x faster.
  • Requires a third party library.

Dynamic

Finally, we can just let the dynamic language runtime do all the work:

public void Dispatch(List<objects> objects)  
{
  foreach (var obj in objects)
  {
    if (obj == null)
    {
      processors.ProcessUnknownType();
    }
    else
    {
      processors.Process((dynamic) obj);
    }
  }
}

Advantages

  • Built into .Net 4.0 and later, no library needed.
  • About 10-20% faster than ImpromptuInterface.
  • No magic string name for method.
  • No additional code needed for overloads.

Disadvantages

  • There are a few "gotchas" when using dynamic (see below).
  • Direct type matching is still significantly faster.

Dynamic Gotchas

In addition to those mentioned by Jon Skeet, here are some I have found:

public void MayNotBeObvious()  
{
  int? aNullableNumber = 3;
  Process((dynamic)aNullableNumber);

  int nonNullableNumber = 42;
  Process((dynamic)nonNullableNumber)
}

public void Process(int? value)  
{
  //neither will match this one
}

public void Process(int value)  
{
  //both match this one
}

Explict casting in the dynamic example is much faster (2X) than using dynamic in the foreach.

//This is faster
public void Dispatch(List<objects> objects)  
{
  foreach (var obj in objects)
  {
    if (obj == null)
    {
      processors.ProcessUnknownType();
    }
    else
    {
      processors.Process((dynamic) obj);
    }
  }
}

//This is much slower
public void Dispatch(List<objects> objects)  
{
  foreach (dynamic obj in objects)
  {
    if (obj == null)
    {
      processors.ProcessUnknownType();
    }
    else
    {
      processors.Process(obj);
    }
  }
}

Benchmarks

Using a simple console app I created (with a slighlty more complex example), here are some simple StopWatch measured benchmarks for 200,000 iterations:

  • Type Matching: .42s
  • Dynamic: 3.06s
  • ImpromptuInterface: 3.63s
  • Reflection: 6.48s

Conclusion

When using the reflection, ImpromptuInterface, or dynamic approach, the overload that accepts the type object is the "default" method. That is to say, if no other suitable overload is found, it will use the one that takes object. There is a lot of interesting behavior with respect to structs in addition to what I listed above. Take special care when using structs with these approaches, especially core structs like byte, int, double, float, etc.

In all cases, null is a special case that must be handled explictly since it lacks type information and will lead to an exception in all expect a few narrow cases (none of which will match actual type).