使用泛型进行多次调度

Posted

技术标签:

【中文标题】使用泛型进行多次调度【英文标题】:Multiple Dispatch with Generics 【发布时间】:2013-01-28 06:05:56 【问题描述】:

我试图通过提供使用泛型的工厂/构建器来抽象出我的接口实现。但是,我在运行时遇到了多个调度和 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&lt;IAddressModel&gt;() 在运行时从泛型分派的 BuildModel(...) 方法始终是 T 的最小派生形式,在这种情况下始终是对象。

但是,如果您调用new Factory().BuildModel(default(IAddressModel));,则会调度正确的方法(很可能是因为这是在编译时完成的)。似乎使用泛型的动态分派不会检查最派生类型的方法,即使调用的方法应该是相同的,无论它是在编译时还是运行时完成。理想情况下,我想将 BuildModel(...) 方法设为私有,并且只公开通用方法。是否有另一种方法可以让动态调度在运行时调用正确的方法?我尝试将 BuildModel&lt;&gt;() 实现更改为 return BuildModel((dynamic)default(T)) 但这会引发运行时错误,即无法确定要分派的方法。有没有办法通过逆变和更多接口来做到这一点?

【问题讨论】:

你考虑过new Factory&lt;IAddressModel&gt;().BuildModel()吗? @Bobson - 是的,我尝试将泛型移动到类级别而不是方法级别,这与运行时调度没有任何区别。 我不认为这是运行时与编译时的问题。据我所知,除非您使用dynamic,否则泛型都在编译时解析,就像常规类型一样。而且您不能使用dynamic,因为default(T) 将是null,因此它无法确定它应该是什么。我怀疑这就是为什么工厂的标准是使用switch... 【参考方案1】:

您也许可以根据参数类型 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();
    

【讨论】:

【参考方案2】:

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

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 成为一个更加派生的参数(协变量),它不是类型安全的。换句话说,C# 不支持协变参数类型,因为它不是类型安全的。

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

    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;
    

【讨论】:

以上是关于使用泛型进行多次调度的主要内容,如果未能解决你的问题,请参考以下文章

在python中进行多次调度的简单方法? (没有外部库或类构建?)

Python 泛型函数调度

如何防止云调度器多次触发一个函数?

(嵌套?)多次调度 [访客模式]

什么是 - 单次和多次调度(与 .NET 相关)?

为许多类似功能实现多次调度的有效方法