【问题标题】: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...

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


【解决方案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 是 IAddressModel 或 IUserModel,除非您定义了这样的限制:

    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;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-15
      • 2015-06-25
      相关资源
      最近更新 更多