Complex inheritance with generics - c#

Let's say I have a set of classes/interfaces:
class ObjectData { }
class UnitData : ObjectData { }
class Component1Data : UnitData { }
class Component2Data : UnitData { }
interface IObject { }
interface IUnit : IObject { }
interface IComponent1 : IUnit { }
interface IComponent2 : IUnit { }
abstract class Object<D, O, I>
where D : ObjectData
where O : Object<D, O, I>, I, new()
where I : IObject
{ }
The point of primary interest here is an Object class which is a base generic class in some hierarchy. Type-param "O" is a way to specify type of an actual class which is derived from the Object. Thus, something like this can be declared and compiled w/o problems:
class Unit : Object<UnitData, Unit, IUnit>, IUnit { }
But what i want to do, is to define another generic "2nd-level" class derived from Object that should also behave as a base class for a couple of similar "3rd-level" entities. And it have to be non-abstract because it is also some sort of an entity. So i need to define something like this:
class Unit<D, I> : Object<D, Unit<D, I>, I>, IUnit
where D : UnitData
where I : IUnit
{ }
class Component1 : Unit<Component1Data, IComponent1>, IComponent1 { }
class Component2 : Unit<Component2Data, IComponent2>, IComponent2 { }
And it produces following compilation error:
error CS0311: The type 'Unit<D, I>' cannot be used as type parameter 'O' in the generic type or method 'Object<D, O, I>'. There is no implicit reference conversion from 'Unit<D, I>' to 'I'.
The question is why? In my vision if Unit<D, I> is implementing IUnit, and param "I" is specified as where I : IUnit, then all should be fine. That's how i see it. What i don't see?

Simplify the problem;
interface IObject { }
interface IUnit : IObject { }
interface IFoo : IUnit { }
abstract class Object<O, I>
where O : Object<O, I>, I, new()
where I : IObject
{}
class Unit : Object<Unit, IUnit>, IUnit
{
}
This is happy and compiles. I've replaced I here with IUnit. Now change to something more derived:
class Unit : Object<Unit, IFoo>, IUnit
and you get the error:
The type 'Unit' cannot be used as type parameter 'O' in the generic
type or method 'Object'. There is no implicit reference
conversion from 'Unit' to 'IFoo'.
So... Unit, which derives from IUnit cannot be converted to IFoo even though both implement IUnit... because Unit doesn't derive from IFoo... which is a condition on Object:
where O : Object<O, I>, I`
which requires you to do something you're not allowed to do :
class Unit<I> : Object<Unit<I>, I>, I

Like others have commented, your generics are too complex.
At least, it seems to me, that there's no need for I type parameter, because your Objects will implement corresponding interface.
So, the code could be simplified like this:
abstract class Object<D, O> : IObject
where D : ObjectData
where O : Object<D, O>
{
}
class Unit<D> : Object<D, Unit<D>>, IUnit
where D : UnitData
{
}
Without full understanding, how O will be used inside of Objects hierarchy, it is hard to say, is it possible to throw away O type parameter.
You've mentioned a static factory method - this is definitely not a reason to bring such complexity. But, of course, you know more about use cases.

Related

C# Derive From Generic Base Class (T : U<T>)

I am trying to find a way to derive a class from a generic base class. Say:
sealed public class Final : Base<Something>
{
}
public class Base<T> : T
where T : Anything // <-- Generics do not allow this
{
}
In C# this does not seem to be possible.
Is there any other solution to achieve something similar to this?
I found this StackOverflow question, but it doesn't seem to solve the issue, or at least I do not understand how it should.
EDIT:
The result I'd like to get is to be able to do something like that:
Anything[] anything;
//Assign some Instances to anything
foreach(Final final in anything){
//do something with final
}
The result I'd like to get is to be able to do something like that:
Anything[] anything;
//Assign some Instances to anything
foreach(Final final in anything){
//do something with final
}
Your foreach loop suggests this: class Anything : Final { … }.
This obviously turns around the inheritance hierarchy as you planned and named it. (You cannot have cycles in your inheritance relationships).
public class Base<T> : T where T : Anything { …
Let me elaborate on this part for a bit. I'll reduce your example even further to just class Base<T> : T.
This is not possible, for good reason. Imagine this:
class Base<T> : T
{
public override string Frobble()
{
Fiddle();
return "*" + base.Frobble() + "*";
}
}
class A
{
public sealed string Frobble() { … }
}
class B
{
}
class C
{
public virtual string Frobble() { … }
}
abstract class D
{
public abstract void Fiddle();
public virtual string Frobble() { … }
}
class E
{
public void Fiddle() { … }
public virtual string Frobble() { … }
}
You get all kinds of absurd situations if class Base<T> : T were allowed.
Base<A> would be absurd because Frobble cannot be overridden in a derived class.
Base<B> would be absurd because you cannot override a method that
doesn't exist in the base class.
Base<C> doesn't work because there is no Fiddle method to call.
Base<D> would not work because you cannot call an abstract method.
Only Base<E> would work.
How would the compiler ever know how to correctly compile Base<T> and analyse code that depends on it?
The point is that you cannot derive from a class that is not known at compile-time. T is a parameter, i.e. a variable, a placeholder. So class Base<T> : T is basically like saying, "Base<T> inherits from some (unknown) class". Class inheritance is a type relationship that requires both involved types to be known at compile-time. (Actually, that's not a super-precise statement because you can inherit from a generic type such as class SpecialList<T> : List<T>. But at the very least, the derived class has to know what members (methods, properties, etc.) are available in the base class.)
Is this what you want?
sealed public class Final : Base<int>{
}
public class Base<T> {
}
You could only do this if Final would be a generic class as well, like so:
public sealed class Final<T> : Base<T>
Then you can put a type restraint on T as either a class, to allow only reference types as T, or an instance of Base<T>, to allow only types that derive from Base<T>:
public class Base<T> where T : Base<T>
I don't know the context of this question, but I ran into same question with a project where I had to make it possible to extend the base class which is already derived by many others. Like:
abstract class Base {}
class FinalA : Base {}
class FinalB : Base {}
// Now create extended base class and expect final classes to be extended as well:
class BetterBase : Base {}
The solution was to create common ancestor and connect through properties:
abstract class Foundation {}
abstract class Base : Foundation
{
Foundation Final { get; }
}
class FinalA : Foundation {}
class FinalB : Foundation {}
class FinalC : Foundation
{
Foundation Base { get; }
}
// Here's the desired extension:
class BetterBase : Base {}
Now BetterBase has connection to final class and if needed, the final classes could have connection with (Better)Base also, as shown in FinalC class.

Partially applying generics for type constraints

I currently try to construct a generic interface that every (generic) class deriving it will have a method accepting a delegate that accepts the type parameter and returns another class of the same type, with only another type parameter.
I tried the following:
public interface GenericInterface<out T, out SomeDerived>
where SomeDerived<T> : GenericInterface<T, SomeDerived>
{
SomeDerived<NT> bind<NT>(bindee<T, NT, SomeDerived<NT>> bindFunc);
}
public delegate AnotherDerived<T2> bindee<in T1, out T2, out AnotherDerived>(T1 param)
where AnotherDerived<T2> : GenericInterface<T2, AnotherDerived>;
public class Derived<T> : GenericInterface<T, Derived>
{
Derived<NT> bind<NT>(bindee<T, NT, Derived<NT>> bindFunc);
}
But it fails to compile and I get this error:
Invalid token '<' in class, struct, or interface member declaration
What is the correct design in such case?
EDIT:
I understand the syntatic reason for the compiler errors. You cannot apply a generic type argument a parameter in a where clause.
I am asking what is the best way to mimic such behavior.
I'll go out on a limb here and say what you're trying to do here with the Generic is impossible; I'll remove if someone thinks I'm wrong.
So lets start with this
interface IFoo<T> where T : IFoo<T>{}
class Foo<T> : IFoo<T> where T : IFoo<T>{}
class Bar<T> : Foo<T> where T : IFoo<T>{}
Lets try to instanciate this;
var foo = new Foo< Bar< ....errr what now? ad infinitum...
So to fix this, you need to redesign so you're classes looks more like this:
interface IBase {}
interface IFoo<out T> where T : IBase { }
class Foo<T> : IFoo<T> where T : IBase { }
which then allows:
IFoo<IBase> foo = new Foo<Base>();
[Addendum]
You can have function level generics that let you get around problems like these...
interface IFoo<out T> where T : IBase
{
IFoo<TBind> Bind<TBind>(Action<T, TBind> bindFunc) where TBind : IBase;
}

Derived type of generic base class

I have the following code.
class Header<T> where T: IItem { }
class HeaderA : Header<ItemA> { }
class HeaderB : Header<ItemB> { }
interface IItem { }
class ItemA : IItem { }
class ItemB : IItem { }
Header<IItem> h = new HeaderA();
The last line cannot be compiled.
Cannot implicitly convert type 'UserQuery.HeaderA' to 'UserQuery.Header<UserQuery.IItem>'
HeaderA is a subtype of Header and ItemA is a subtype of IItem. Why it doesn't work?
In short, you're trying to use a concept called covariance, which is not supported in .NET generic classes, and not supported by default in interfaces.
If you want to allow the class to do this, you can specify it in C# 3 or later using the out contextual keyword on a generic interface:
interface IHeader<out T> where T : IItem { }
class Header<T>: IHeader<T> where T:IItem { }
class HeaderA : Header<ItemA> { }
class HeaderB : Header<ItemB> { }
interface IItem { }
class ItemA : IItem { }
class ItemB : IItem { }
public void Foo()
{
//now this works; notice the use of the interface instead of the base class.
IHeader<IItem> h = new HeaderA();
}
By using the interface with the keyword, you are basically telling the compiler that no usage of the interface will ever have to know more about the generic type than that it meets the constraints of the interface's generic type declaration (or that it's an object). As such, while you can now assign more derived generics to variables of the interface type, you can only deal with them as the interface type, never as any derived type.
The out keyword is not acceptable for class definitions; you cannot force usages of Header<T> to be covariant.

C# assigning a derived class to a base class variable with generics

I have a set of classes and interfaces which have a relatively simple hierarchy, however having things inherit and then having a variable which accepts the base class is not working well. I think the problem is that my dynamic types MUST be where : class and not where : IEntity because these types are used in functions which I have no ability to change.
public interface IB<X, Y>
where X : class, IEntity
where Y : class, IEntity
{
...
}
public abstract class A
{
...
}
public abstract class B<X, Y> : A, IB<X, Y>
where X : class, IEntity
where Y : class, IEntity
{
...
}
public interface IEntity
{
...
}
public class Entity1 : IEntity
{
...
}
public class Entity2 : IEntity
{
...
}
public class C : B<Entity1, Entity2>, IB<Entity1, Entity2>
{
...
}
And then, attempting to use all this in a function...
C c = new C();
IB<IEntity, IEntity> ib = c;
Cannot implicityly or explicity cast this.
How can I get this to work?
Your interface is not covariantly valid. That is to say,
IB<IEntity, IEntity> and IB<Entity1, Entity2> are not the same thing. To fix this, you can use the out keyword on the type parameters in the interface definition
public interface IB<out X, out Y> where X : class where Y : class
As a secondary point, you need to cascade your constraints to class B
public abstract class B<X, Y> : A, IB<X, Y> where X : class where Y : class
With these changes in place, your assignment of C into ib is legal.
C c = new C();
IB<IEntity, IEntity> ib = c;
(And as Davy8 correctly points out in the comments, this interface covariance feature requires C# 4+.)
You will need to change the definition of class B as follows:
public abstract class B<X, Y> : A, IB<X, Y> where X : class where Y : class
{
...
}
Constraints have to be specified throughout the inheritance chain.

Generic type parameter constraint based on other type parameter in the same definition

I have type hierarchy defined like this:
interface IMyClass
{
}
interface IBase1<T>
{
}
interface IBase2<T>
{
}
interface IMyDerived1 : IBase1<IMyClass>
{
}
class Base1<T, U> : IBase1<T>
where U : IBase2<T>
{
}
class Base2<T, U> : IBase2<T>
where U : IBase1<T>
{
}
class Derived1<T, U> : Base1<T, U>, IMyDerived1
where T : IMyClass
where U : IBase2<T>
{
}
class Derived2<T, U> : Base2<T, U*>
where T : IMyClass
where U : IMyDerived1
{
}
but Visual Studio 2008 (.net 3.5 SP1) says that parameter U in parent specifier of Derived2 (marked with *) is not convertible to IBase1<T>. Is this solvable?
EDIT:
It indeed looks like generics overuse but allows Base1,2 and Derived1,2 to apply operations on supplied types without a casts. Something like this:
class MyClass : IMyClass
{}
class MySpecific1 : Derived1<MyClass, MySpecific2>
{
// use inherited properties and methods of type MyClass here
// use properties of MySpecific2 returning MyClass without casts
}
class MySpecific2 : Derived2<MyClass, MySpecific1>
{
// use inherited properties and methods of type MyClass here
// use properties of MySpecific1 returning MyClass without casts
}
Probably this can be solved more elegantly with variance in .net4 but I'm stuck with 3.5 for now.
class Derived2<T, U>: Base2<T, U>
where T: IMyClass
where U: IMyDerived1, IBase1<T>
{
}
That hurt my head!
Having a look at it, the problem lies with this definition:
interface IMyDerived1 : IBase1<IMyClass>
{
}
You've specialised that generic implementation, and then attempted to use with generic arguments later on:
class Derived2<T, U> : Base2<T, U>
where T : IMyClass
where U : IMyDerived1
{
}
Which is invalid. Not sure if this is correct, but can you make either this change:
interface IMyDerived1<T> : IBase1<T>
{
}
class Derived2<T, U> : Base2<T, U>
where T : IMyClass
where U : IMyDerived1<T>
{
}
That's a complicated hierarchy you're designing there, what will be its use?
The problem is, in Derived2, T is not IMyClass, it could be some other class implementing this interface. In Base1, it is specified to be exactly IMyClass. Types with different generic arguments are not compatible in C# 3.0.
For me, this looks a bit like generic overuse. But I can't see the context.

Categories

Resources