Double dispatcher in C#
Tags: csharp, dotnet, programming
Published: 2021-01-28 08:22:04

Double dispatcher is a feature of some programming languages that allows the runtime to decide which method to invoke/dispatch based on passed arguments type.

### Single dispatch

In all C-like programming languages, like C#, `virtual` is used to facilitate polymorphic behavior. Usage of `virtual` is a special type of dispatch know as `single dispatch`. In single dispatch which method will be invoked is decided based on the invoker object's type which is known only in runtime, not compilation time. So for following hierarchy `order.IssueOrder(...)` will invoke `.IssueOrder(..)` from type `PurchaseOrder` provided that `var order = new PurchaseOrder()`.

```csharp
public interface IOrder
{
    Result IssueOrder(IOrder order);
}
public class SalesOrder : IOrder
{
    public Result IssueOrder(IOrder order) => Result.Ok();
}
public class PurchaseOrder : IOrder
{
    public Result IssueOrder(IOrder order) => Result.Fail("failed");
}
```

### Why single dispatch is not enough?

Consider the following service:

```csharp
public class OrderIssuerService
{
    public string Issue(IOrder order) => Do(order);

    private string Do(IOrder _) => nameof(IOrder);
    private string Do(PurchaseOrder _) => nameof(PurchaseOrder);
    private string Do(SalesOrder _) => nameof(SalesOrder);
}
```

What will be the output of the following code block?

```csharp

//  private readonly ITestOutputHelper _outputHelper;

[Fact]
public void Order_Issuer_Service_Should_Invoke_Method_Taking_IOrder_As_Argument()
{
    _outputHelper.WriteLine(new OrderIssuerService().Issue(new SalesOrder()));
    _outputHelper.WriteLine(new OrderIssuerService().Issue(new PurchaseOrder()));
}
```

Output
```
IOrder
IOrder
```

The important point to note is none of the overloads that take `PurchaseOrder` or `SalesOrder` has been invoked. That's because **method invocation in C# doesn't depend on the passed argument's type only on the invoker's type**.


### How can we make C# to invoke the overloads for `PurchaseOrder` and `SalesOrder`?

Approach one: Check for `order` arguments real type as following 

```csharp
public string Issue(IOrder order)
{
    return order.GetType() == typeof(PurchaseOrder)
        ? Do(order as PurchaseOrder)
        : Do(order as SalesOrder);
}
```

But checking a type like this is not recommended. It in fact points out a flaw in the design but that's a different topic. So we don't want to cast as such what can we do?

Approach two: Use dynamic keyword

```csharp
public class OrderIssuerService
{
    public string Issue(dynamic order) => Do(order);

    ...
    ...
}
```

In this case, C# runtime will invoke the proper overload because `dynamic` variable holds the original type which is `PurchaseOrder`/`SalesOrder`.

### Is dynamic recommended to be used in this case?

No. `dynamic` is generally not a good approach to solve this problem. There are other ways to resolve the issue using `visitor pattern` but `dynamic` works :) 

### An example where employing double dispatcher can be valuable

Consider the following scenario:

```csharp
public class OrderIssuer
{
    public void Issue(IOrder order) 
    { 
        // complex legacy logic

        MutateOrder(order); 

        // complex legacy logic
    }

    private void MutateOrder(IOrder _) { // complex legacy logic }
}
```

Let's say there's a bug in the method `MutateOrder` but it's legacy code and you don't want to touch any of the methods now (maybe your boss forbade you to do so or you are on a deadline). If you know the fix is related to type `PurchaseOrder` then **maybe** following can work for you (of course it depends on the code in `MutateOrder`)

```csharp
public class OrderIssuer
{
    public void Issue(IOrder order) 
    { 
        // complex legacy logic

        MutateOrder(order); 

        // will invoke the new overload as order is of 
        MutateOrder((dynamic)order); 

        // complex legacy logic
    }

    private void MutateOrder(IOrder _) { // complex legacy logic }
    private void MutateOrder(PurchaseOrder _) { // new logic }
}
```

This approach obviously has the downside that for `SalesOrder` 1st overload will be invoked twice but the point is without changing anything in the legacy code we might be able to fix the bug albeit the approach is a dirty approach.