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()
.
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:
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?
// 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
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
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:
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
)
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.
Top comments (1)
Thorough explanation