Reputation: 3829
I have 2 classes with the following declarations:
abstract class ClassBase<T, S> where T : myType where S : System.Data.Objects.DataClasses.EntityObject
abstract class ServiceBase<T> where T : myType
and I have 2 other classes, that inherit one from each, we can call ClassInherited and ServiceInherited. Note that the two Service classes are not in the same project as the other two.
The idea is that in the ServiceBase
class I can declare a property like protected ClassBase<T,System.Data.Objects.DataClasses.EntityObject> Class { get; set; }
and then in the inherited service`s constructor something like this.Class = ClassInheritedInstance
I already implemented the idea but it gives me this error when assigning the Class property in the ServiceInherited class constructor:
Cannot implicitly convert type 'ClassInherited' to 'ClassBase< T, S>'
Note that ClassInherited is indeed an specification of Class<T,S>
... it's just that the compiler doesn't seem to be able to tell the types correctly. Also changing the declaration of the class property to protected ClassBase<T, EntityObjectInherited>
works, and EntityObjectInherited is an implementation of System.Data.Objects.DataClasses.EntityObject
... I don't see why is there a problem.
Note that at compile time the type of ClassInherited
is known, as its declaration is public class ClassInherited : ClassBase<myTypeInherited, EntityObjectInherited>
Upvotes: 0
Views: 235
Reputation: 22945
The reason that you cannot use protected ClassBase<T,S> Class { get; set; }
in the ServiceInherited-class is that you do not know the S-type that is needed to declare a type of the property Class.
You have to options:
Include the S type in the specification of the Service-type: abstract class ServiceBase<T, S> where T : myType where S : System.Data.Objects.DataClasses.EntityObject
Implement an interface for ClassBase with only the T-type, so that you can refer to a class-inherited-object without using the S-type. Then you CAN have a property in the service class (of the interface-type), since you do not need to specify the S-type.
Note that generic-type-checking is not checked at run-time, but at compile-time. Else it wouldn't be strong-typing.
The reason the cast won't work is that type ClassBase<T, EntityObjectInherited>
is not equal or castable to ClassBase<T, System.Data.Objects.DataClasses.EntityObject>
. Covariance doesn't work on class-types, only on interface-types.
I think the solution here is to work with interfaces. Use an interface for class-base, say IClassBase<T>
. That way you can omit the S-type in the signature of the class, and only have it in the interface.
One thing you can do is to create an interface for the Class property. You can define the following interface.
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.
}
If you implement this interface on your ClassBase class, and add a constructor on your ServiceBase class which accepts an object of type IClass, then you can push this object to property Class in the base-class. Like this:
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) {
}
}
One thing to note, is not to expose the S-type of the ClassBase to the interface. Since you do not want the Service-classes to know this type, they cannot actively call any methods or use properties that somehow have the S-type in their definition.
Upvotes: 1
Reputation: 183
This ugly boxing, unboxing should work :
Class = (ClassBase<T, S>)(object)new ClassInherited();
Covariance is allowed only with generic interface today ?MSDN
This works :
// 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;
This doesn't :
List<string> strings = new List<string>();
List<object> objects = strings;
Upvotes: 1
Reputation: 13374
The compiler won't be able to guess that ClassInherited is indeed a correct match for ClassBase<T, S> as it does not know the exact types of T and S, that will be decided at generics type instanciation at runtime.
So if you're sure that at runtime the types will be compatible you can safely try a cast :
Class = ClassInheritedInstance as ClassBase<T, S>
This will only have a slight (not to say negligible) overhead as the CLR will need to check the compatibility of the types to have safe code.
Upvotes: 0