具有泛型的多个调度

时间:2013-02-12 20:11:18

标签: c# generics dynamic-dispatch multiple-dispatch

我正在尝试通过使用泛型提供工厂/构建器来抽象我的接口实现。但是,我遇到了运行时多个调度和C#泛型的问题,这些问题似乎很奇怪。

基本场景是我定义了几个接口:

public interface IAddressModel
{
}

public interface IUserModel
{
}

然后我有一个工厂类来返回实际的实现:

public class Factory
{
    public T BuildModel<T>()
    {
        return BuildModel(default(T));
    }

    public object BuildModel(object obj)
    {
        //this is here because the compiler will complain about casting T to
        //the first inteface parameter in the first defined BuildModel method
        return null;
    }

    public IAddressModel BuildModel(IAddressModel iModel)
    {
        //where AddressModel inherits from IAddressModel
        return new AddressModel();
    }

    public IUserModel BuildModel(IUserModel iModel)
    {
        //where UserModel inherits from IUserModel
        return new UserModel(); 
    }
}

问题是当工厂被这样调用时:new Factory().BuildModel<IAddressModel>() 在运行时从泛型调度的BuildModel(...)方法始终是T的最小派生形式,在这种情况下始终是对象。

但是,如果调用new Factory().BuildModel(default(IAddressModel));,则会替换正确的方法(很可能因为这是在编译时完成的)。看起来使用泛型的动态调度不检查大多数派生类型的方法,即使调用的方法在编译时或运行时完成也应该相同。理想情况下,我想将BuildModel(...)方法设为私有,只显示泛型方法。还有另一种方法可以让动态显示在运行时调用正确的方法吗?我已经尝试将BuildModel<>()实现更改为return BuildModel((dynamic)default(T)),但这会导致运行时错误,无法确定要分派的方法。有没有办法用逆变和更多接口来做到这一点?

2 个答案:

答案 0 :(得分:2)

您可以根据参数类型T自行调度:

public class Factory
{
    private Dictionary<Type, Func<object>> builders = new Dictionary<Type, Func<object>>
    {
        { typeof(IAddressModel), BuildAddressModel },
        { typeof(IUserModel), BuildUserModel }
    };

    public T Build<T>()
    {
        Func<object> buildFunc;
        if (builders.TryGetValue(typeof(T), out buildFunc))
        {
            return (T)buildFunc();
        }
        else throw new ArgumentException("No builder for type " + typeof(T).Name);
    }

    private static IAddressModel BuildAddressModel()
    {
        return new AddressModel();
    }

    private static IUserModel BuildUserModel()
    {
        return new UserModel();
    }
}

答案 1 :(得分:1)

代码的当前状态需要显式转换才能编译。

public T BuildModel<T>()
{
    return (T)BuildModel(default(T));
}

BuildModel将T多态地视为对象。除非您定义了这样的限制,否则BuildModel不知道T是IAddressModelIUserModel

public T BuildModel<T>() where T: IAddressModel
{            
    Console.WriteLine(typeof(T));
    return (T)BuildModel(default(T));
}

现在,编译器有足够的信息来识别T是IAddressModel。但是,你所追求的是object成为一个更加派生的参数(covarient),它不是类型安全的。换句话说,C#不支持covarient参数类型,因为它不是类型安全的。

您仍然可以通过条件逻辑实现工厂行为:

    public T BuildModel<T>()
    {
        T result = default(T);

        if (typeof(T) == typeof(IAddressModel))
            result = (T)BuildModel((IAddressModel)result);
        else if (typeof(T) == typeof(IUserModel))
            result = (T)BuildModel((IUserModel)result);

        return result;
    }