1

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?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
drty
  • 200
  • 1
  • 11
  • 5
    this has to be the most convoluted use of generics I've ever seen... – Meirion Hughes Sep 28 '15 at 13:00
  • How `O` will be used inside of `Object`? Post the sample code, please. – Dennis Sep 28 '15 at 13:01
  • Like this for example: public static O Create(D data) { return data != null ? new O { Data = data } : null; } public D Data { get; private set; } – drty Sep 28 '15 at 13:07
  • 3
    I'd like to quote Eric Lippert (an authority on C#, since I am not myself one): "More specifically, I would say that you are abusing generics. The fact that you've made type relationships that are too complicated for you to analyze yourself is evidence that you ought to simplify the whole thing; if you're not keeping all the type relationships straight and you wrote the thing then your users surely will not be able to keep it straight either." http://stackoverflow.com/questions/17434851/why-does-a-generic-type-constraint-result-in-a-no-implicit-reference-conversion – p e p Sep 28 '15 at 13:11
  • Maybe Eric is right, but does this answer my question? – drty Sep 28 '15 at 13:13
  • Hence why that was a comment, and not an answer. – p e p Sep 28 '15 at 13:14
  • @drty: so, all this hell is just for having static factory method in base class? Are you kidding? – Dennis Sep 28 '15 at 13:28
  • No, i'm not. There are actually much more uses of "O" in real implementation. It was just an example. "O" is needed. W/o it it's losing some flexibility. – drty Sep 28 '15 at 13:30

2 Answers2

1

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.

Dennis
  • 37,026
  • 10
  • 82
  • 150
  • "I" is defined to implement IEquatable and IComparable on Object. Also there is an Collection class nested within the Object which is implementing IEnumerable. Yes, i know this hierarchy turned out to be too complex. And i've specified this in the caption. What i want to understand is the reason of an error - it doesn't look rational to me. But thanks for your answer anyway. Maybe I really need to simplify all of this. – drty Sep 28 '15 at 13:52
1

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
Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
  • Honestly, i can't grasp the second part of your post. And i don't see how replacing of actual classes with interfaces can help me here, btw. – drty Sep 28 '15 at 14:16
  • Guys, you are trying to show me how to simplify the model. But sadly, it's not the question i've asked in first place. – drty Sep 28 '15 at 14:19
  • 1
    Its a simplification resulting in the same error (I'm on visual studio 2015 and wording seems different). Basically I believe your problem is the compiler isn't able to convert `Unit to I` because the conditions require that `Unit` *is* `I` and the above shows that in simpler terms. – Meirion Hughes Sep 28 '15 at 14:20
  • But why? Unit is implementing IUnit, and "I" (following the mentioned constraint) is something which is IUnit or derived from IUnit. Isn't it? – drty Sep 28 '15 at 14:24
  • I'd guess its because it can't do covarience. If you fix I to IUnit it becomes happy. `class Unit : Object, IUnit>, IUnit` – Meirion Hughes Sep 28 '15 at 14:41
  • Well... It is hard to follow your reasoning when you continually updating your post, but I can agree with the conclusion in your last edit. Still your variant of simplification doesn't look to me logically equal to the initial code. If we replace definition of Unit to the one which is closer to my model: `class Unit : Object, I>, IUnit where I : IUnit { }`, then I still don't see why compiler can't convert `Unit` to `I` (where `I` is `IObject`, and as we know `Unit` is also derived from `IObject`). – drty Sep 28 '15 at 15:29
  • Because the condition: `where O : Object, I` requires `Unit` implement `I`...and it never will, because `I` can be something *more* derived than `IUnit`. – Meirion Hughes Sep 28 '15 at 15:36