使用具有不同约束的通用类

时间:2015-11-20 08:55:27

标签: c# generics

我有两个遗留的C#用户控件,我需要一起工作。 我有一个现有的对话框,我需要添加一个现有的通用用户控件。

我试图对下面的层次结构进行抽样

interface Foo<T> {}
interface Bar<T> 
{
    T DataObject { get; set; }
}

public class ClassA<T> where T : Foo<T>
{
    public ClassA(T dataObject)
    {
        //Do stuff if T implements Bar<T> - Pseudocode ahead
        if(var T is Bar<T>)
        {
            var x = new ClassB<T>();
            //x is typesafe, and I can set DataObject
            x.DataObject = dataObject;
        }
   }
}

public class ClassB<T> where T : Bar<T> 
{
   T DataObject { get; set; }
}

现有对话框ClassA目前没有任何通用约束,但可以轻松更改为要求T实施Foo<T>。 userControl ClassB基于另一个界面Bar<T>。在实践中,Bar<T>个对象总是实现Foo<T> - 理论当然不是。

我是否可以使用任何构造来声明ClassB类型的对象,并获得编译时验证?

上面的结构会给我一个编译错误说明:

  

类型&#39; T&#39;不能用作类型参数&#39; T&#39;在泛型中   方法ClassB<T>。没有隐式引用转换   &#39; T&#39; Bar<T>

我可以使用Reflection创建ClassB对象,也可以使用Reflection设置属性 - 但我更喜欢编译时解决方案。

但是在我目前的情况下有两个现有的对话框 - 我不确定我能够做到。

任何帮助都会受到赞赏 - 也就是说它能说明我的期望,它无法完成。

- 编辑

试着详细说明一下。 当我有ClassC同时实现Foo<T>Bar<T>时,问题就会出现

public class ClassC<T> : Foo<T>, Bar<T>
{
   T DataProperty
}

如果我创建ClassA<ClassC>的实例,那个特定实例中的TClassC - 那么我可以在代码中使用T来创建ClassB的一个实例 - 在这种情况下,T中的ClassA确实符合ClassB约束,因为TClassC

我没有想出如何或如果可能 - 倾向于相信我不能。

正如我上面所写,我有一个基于反射的解决方案,我不喜欢使用反射并仅获得运行时验证。但在这种情况下,需要协同工作的两个遗留对象可能会耗尽选项。

2 个答案:

答案 0 :(得分:0)

首先,你的类型有点奇怪。它们是递归的,ClassB<T>要求T实现Bar<T>,其结构与ClassB<T>相同。也许您打算ClassB<T> 实施 Bar<T>而不是将其作为类型参数?

无论如何你不能这样做。为了能够编写ClassB<T>,编译器需要确保T是运行时ClassB<> 的有效类型参数。仅当ClassA<T> T上的类型参数至少与ClassB<T>中的类型参数一样具有限制性时,才会出现这种情况。

不幸的是,即使是确保这种情况的硬类型检查也不允许您编写ClassB<T>

因此,无法编写ClassB<T>,您将无法在编译时获得静态类型安全性。因此,即使您创建了ClassB<T>的实例(您可以),您也无法访问DataProperty,因为您无法将其转换为ClassB<T>

因此,为了解决这个问题,您可能必须仅使用反射访问DataProperty,或者调用具有类型约束的ClassA<T>内的方法。我会告诉你两个解决方案:

public class ClassA<T>
    where T : Foo<T>
{
    public ClassA(T dataObject)
    {
        if (typeof(Bar<T>).IsAssignableFrom(typeof(T)))
        {
            // method 1, calling a generic function
            MethodInfo mi = typeof(ClassA<T>).GetMethod("SetBDataObject").MakeGenericMethod(typeof(Bar<T>));
            mi.Invoke(this, new object[] { dataObject });

            // method 2, doing it all with reflection
            Type type = typeof(ClassB<>).MakeGenericType(typeof(T));
            object x = Activator.CreateInstance(type);
            type.GetProperty("DataObject").SetValue(x, dataObject);
        }
    }

    public object SetBDataObject<TB> (TB obj)
        where TB : Bar<TB>
    {
        var x = new ClassB<TB>();
        x.DataObject = obj;
        return x;
    }
}

答案 1 :(得分:0)

您的代码中可能令人困惑的第一件事是,您在TClassA<T>类中使用了与ClassB<T>相同的字母{。}}。

我首先要说明显而易见的事: 当您致电var x = new ClassB<T>();T的约束位于ClassA<T>(即T : Foo<T>)的上下文中,而new ClassB<T>()期望T匹配T : Bar<T>的约束。

在我看来,您遇到问题的根本原因是设计问题。看起来您在typesclasses之间有点混淆。

让我们一起来看看:
来自Gang of Four Design Patterns book

  

对象的定义了对象的实现方式。该类   定义对象的内部状态及其实现   操作

     

相反,对象的类型仅指其界面 - 集合   可以回复的请求。

     

对象可以有多种类型,不同类的对象可以有   相同的类型。

代码中接口的使用意味着针对types进行编码(这很好!) 检查if (dataObject is Bar<T>)并根据结果构建ClassB<U>,其中!typeof(U).Equals(typeof(T)意味着严重依赖于实施(例如class)。

如果你问我,我认为你应该尝试下列之一:

  1. 使用factory pattern构建ClassB。在专用工厂中,您可以添加一些逻辑和验证,以便决定构建它的 (从您的代码中,由于类型不匹配,因此不清楚)。

  2. 如果可能,解析Foo<T>Foo<T>之间的关系并声明接口中的约束。在这种情况下,两个接口应具有相同的约束fto T