泛型和继承的编译器问题

时间:2012-12-03 22:12:43

标签: c# .net generics inheritance

我有2个类,声明如下:

  
      
  • abstract class ClassBase<T, S> where T : myType where S : System.Data.Objects.DataClasses.EntityObject
  •   
  • abstract class ServiceBase<T> where T : myType
  •   

我还有另外两个类,它们从每个类继承一个,我们可以调用 ClassInherited ServiceInherited 。请注意,这两个Service类与其他两个Service项不在同一个项目中。

我的想法是在ServiceBase类中我可以声明像protected ClassBase<T,System.Data.Objects.DataClasses.EntityObject> Class { get; set; }这样的属性,然后在继承的服务构造函数中声明类似this.Class = ClassInheritedInstance

的属性

我已经实现了这个想法,但是在ServiceInherited类构造函数中分配 Class 属性时,它给了我这个错误:

  

无法将类型'ClassInherited'隐式转换为'ClassBase&lt; T,S&gt;'

请注意,ClassInherited确实是Class<T,S>的规范......只是编译器似乎无法正确地告诉类型。同时将类属性的声明更改为protected ClassBase<T, EntityObjectInherited>,而 EntityObjectInherited System.Data.Objects.DataClasses.EntityObject的实现...我不明白为什么会出现问题。

更新1

请注意,在编译时,ClassInherited的类型已知,因为其声明为public class ClassInherited : ClassBase<myTypeInherited, EntityObjectInherited>

3 个答案:

答案 0 :(得分:1)

初步答案

您无法在ServiceInherited类中使用protected ClassBase<T,S> Class { get; set; }的原因是您不知道声明属性类的类型所需的S类型。

你必须选择:

  • 在服务类型的规范中包含S类型:abstract class ServiceBase<T, S> where T : myType where S : System.Data.Objects.DataClasses.EntityObject

  • 为ClassBase实现一个只有T类型的接口,这样就可以在不使用S-type的情况下引用类继承对象。然后,您可以在服务类(接口类型)中拥有属性,因为您不需要指定S类型。

请注意,泛型类型检查不会在运行时检查,而是在编译时检查。否则它不会是强力打字。

更新

强制转换无效的原因是ClassBase<T, EntityObjectInherited>类型与ClassBase<T, System.Data.Objects.DataClasses.EntityObject>不相等或无法转换。协方差不适用于类类型,仅适用于接口类型。

我认为这里的解决方案是使用接口。使用类库的接口,比如说IClassBase<T>。这样你可以省略类的签名中的S类型,并且只在接口中有它。

更新(2)

您可以做的一件事是为Class属性创建一个接口。您可以定义以下界面。

public interface IClass<T> where T : myType {
    // TODO
    // Define some interface definition, but you cannot use the
    // EntityObject derived class, since they are not to be known 
    // in the service class.      
}

如果在ClassBase类上实现此接口,并在ServiceBase类上添加一个接受IClass类型对象的构造函数,则可以将此对象推送到基类中的属性Class。像这样:

public abstract class ClassBase<T, S> : IClass<T>
    where T : MyType
    where S : EntityObject {
}

public abstract class ServiceBase<T> where T : MyType {
    protected ServiceBase(IClass<T> classObject) {
        Class = classObject;
    }
    protected IClass<T> Class { get; set; }
}

public class ServiceInherited : ServiceBase<MyTypeDerived> {
    public ServiceInherited(IClass<MyTypeDerived> classObject)
        : base(classObject) {
    }
}

需要注意的一点是,不要将ClassBase的S类型暴露给接口。由于您不希望服务类知道此类型,因此它们无法主动调用任何方法或使用在其定义中以某种方式具有S类型的属性。

答案 1 :(得分:1)

这个丑陋的拳击,拆箱应该有效:

Class = (ClassBase<T, S>)(object)new ClassInherited();

今天只允许使用通用接口协方差吗?MSDN

这有效:

// Covariance. 
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument  
// is assigned to an object instantiated with a less derived type argument.  
// Assignment compatibility is preserved. 
IEnumerable<object> objects = strings;

这不是:

 List<string> strings = new List<string>();
        List<object> objects = strings;

答案 2 :(得分:0)

编译器无法猜测 ClassInherited 确实是 ClassBase&lt; T,S&gt; 的正确匹配,因为它不知道<的确切类型< em> T 和 S ,将在运行时在泛型类型实例中确定。

因此,如果你确定在运行时类型兼容,你可以安全地尝试演员:

Class = ClassInheritedInstance as ClassBase<T, S>

这只会产生轻微(不可忽视的)开销,因为CLR需要检查类型的兼容性以获得安全代码。