Delen via


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;

    }

}

 

MultimethodFactory.cs

Comments

  • Anonymous
    November 10, 2008
    I read a couple of interesting articles on the subject of multiple dispatch last night. The first, entitled