Multiple dispatch in C# - MultimethodFactory class
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
internal abstract class MultimethodFactory
{
private enum ParameterKind
{
In,
Out,
Ref
}
private sealed class Dispatcher
{
private readonly Delegate _function;
private readonly Type[] _parameterTypes;
internal Delegate Function
{
get
{
return _function;
}
}
internal Dispatcher(Delegate function, Type[] parameterTypes)
{
Debug.Assert(null != function);
Debug.Assert(null != parameterTypes && parameterTypes.Length > 0);
_function = function;
_parameterTypes = parameterTypes;
}
internal Type GetParameterType(int index)
{
return _parameterTypes[index];
}
// IsCompatibleArgTypeArray
/// <summary>
/// Check compatibility of parameter types versus types of real arguments. Compatibility
/// is based on contravariance of argument types.
/// </summary>
/// <param name="argTypes"></param>
/// <returns></returns>
internal bool IsCompatibleArgTypeArray(Type[] argTypes)
{
Debug.Assert(null != argTypes && argTypes.Length > 0);
Debug.Assert(_parameterTypes.Length == argTypes.Length);
for (int i = 0; i < _parameterTypes.Length; ++i)
{
if (!_parameterTypes[i].IsAssignableFrom(argTypes[i]))
return false;
}
return true;
}
internal bool CompareParameterTypeArray(Type[] parameterTypes)
{
Debug.Assert(null != parameterTypes && parameterTypes.Length > 0);
Debug.Assert(parameterTypes.Length == _parameterTypes.Length);
return CompareTypeArrays(_parameterTypes, parameterTypes);
}
private static bool CompareTypeArrays(Type[] types1, Type[] types2)
{
Debug.Assert(null != types1 && types1.Length > 0);
Debug.Assert(null != types2 && types2.Length > 0);
Debug.Assert(types1.Length == types2.Length);
for (int i = 0; i < types1.Length; ++i)
{
if (types1[i] != types2[i])
return false;
}
return true;
}
}
private readonly int _parameterCount;
private readonly List<Dispatcher> _dispatchers = new List<Dispatcher>();
internal protected MultimethodFactory(int parameterCount)
{
Debug.Assert(parameterCount > 0);
_parameterCount = parameterCount;
}
internal protected void CreateDispatcher(Delegate function)
{
Debug.Assert(null != function);
ParameterInfo[] parameters = function.Method.GetParameters();
if (parameters.Length != _parameterCount)
throw new Exception();
Type[] parameterTypes = new Type[parameters.Length];
for (int i = 0; i < parameterTypes.Length; ++i)
{
Type baseParameterType;
if (ParameterKind.In != GetParameterKind(parameters[i], out baseParameterType))
throw new ArgumentException("\"out\" and \"ref\" parameters not supported");
parameterTypes[i] = baseParameterType;
}
foreach (Dispatcher dispatcher in _dispatchers)
{
if (dispatcher.CompareParameterTypeArray(parameterTypes))
throw new ArgumentException("Parameter types exactly match existing method");
}
_dispatchers.Add(new Dispatcher(function, parameterTypes));
}
internal protected object InternalInvoke(params object[] args)
{
Debug.Assert(null != args);
if (_parameterCount != args.Length)
throw new Exception();
Type[] argTypes = new Type[args.Length];
for (int i = 0; i < argTypes.Length; ++i)
argTypes[i] = args[i].GetType();
List<Dispatcher> compatibleDispatchers = new List<Dispatcher>();
foreach (Dispatcher @delegate in _dispatchers)
{
if (@delegate.IsCompatibleArgTypeArray(argTypes))
compatibleDispatchers.Add(@delegate);
}
if (0 == compatibleDispatchers.Count)
throw new Exception("No compatible method implementation");
Dispatcher dispatcher = GetMostCompatibleDispatcher(compatibleDispatchers.ToArray(), argTypes);
if (null != dispatcher)
return dispatcher.Function.DynamicInvoke(args);
throw new Exception("Ambiguous method implementations");
}
private static ParameterKind GetParameterKind(ParameterInfo parameter, out Type baseParameterType)
{
Debug.Assert(null != parameter);
baseParameterType = null;
if (!parameter.ParameterType.FullName.EndsWith("&", StringComparison.Ordinal))
{
baseParameterType = parameter.ParameterType;
return ParameterKind.In;
}
string parameterTypeName = parameter.ParameterType.FullName;
baseParameterType = Type.GetType(parameterTypeName.Substring(0, parameterTypeName.Length - 1));
return parameter.IsOut ? ParameterKind.Out : ParameterKind.Ref;
}
// GetTypeGenerality
/// <summary>
/// Get generality (inverse of specificity) of derived type compared to base type.
/// </summary>
/// <remarks>
/// 0 means that derived type is exactly the same as base type.
/// </remarks>
/// <param name="baseType"></param>
/// <param name="derivedType"></param>
/// <returns></returns>
private static int GetTypeGenerality(Type baseType, Type derivedType)
{
Debug.Assert(null != baseType);
Debug.Assert(null != derivedType);
Debug.Assert(baseType.IsAssignableFrom(derivedType));
int generality = 0;
Type temp = derivedType;
while (derivedType != baseType)
{
derivedType = derivedType.BaseType;
++generality;
}
return generality;
}
private Dispatcher GetMostCompatibleDispatcher(Dispatcher[] compatibleDispatchers, Type[] argTypes)
{
Debug.Assert(null != compatibleDispatchers && compatibleDispatchers.Length > 0);
Debug.Assert(null != argTypes && argTypes.Length > 0);
if (1 == compatibleDispatchers.Length)
return compatibleDispatchers[0];
int[] generalities = new int[compatibleDispatchers.Length];
for (int i = 0; i < generalities.Length; ++i)
generalities[i] = int.MaxValue;
for (int i = 0; i < _parameterCount; ++i)
{
int minimumGenerality = int.MaxValue;
for (int j = 0; j < generalities.Length; ++j)
{
// Only check generality for method if it hasn't already been eliminated.
if (-1 != generalities[j])
{
generalities[j] = GetTypeGenerality(compatibleDispatchers[j].GetParameterType(i), argTypes[i]);
if (generalities[j] < minimumGenerality)
minimumGenerality = generalities[j];
}
}
int dispatcherCount = 0;
int lastDispatcherIndex = -1;
for (int j = 0; j < generalities.Length; ++j)
{
if (generalities[j] > minimumGenerality)
{
generalities[j] = -1;
}
else if (-1 != generalities[j])
{
lastDispatcherIndex = j;
++dispatcherCount;
}
}
Debug.Assert(lastDispatcherIndex >= 0);
if (1 == dispatcherCount)
// We've found the single most compatible dispatcher.
return compatibleDispatchers[lastDispatcherIndex];
}
return null;
}
}
Comments
- Anonymous
November 10, 2008
I read a couple of interesting articles on the subject of multiple dispatch last night. The first, entitled