2

I don't understand why this doesn't work:

Classes:

public abstract class BaseObj
{

    public bool IsValid => GetValidationErrors().Count == 0;
}

public class BaseObjWithId: BaseObj 
{
    public int Id { get; set; }
}

public class BaseReference<T> : BaseObj where T : BaseObjWithId
{

    public T ObjReference { get; set; }

}

public class Foo: BaseObjWithId
{
    public string Name{get;set;}
}

public class FooRef : BaseReference<Foo>
{

}

Code-Statement:

    BaseReference<BaseObjWithId> foo= new FooRef();

Error CS0029 Cannot implicitly convert type...

This works:

    BaseReference<Foo> foo= new FooRef();

But I don't understand why, because Foo is a BaseObjWithId...

Thank you for your explanation

GrayFox
  • 997
  • 1
  • 9
  • 26
  • 4
    Surely its because Foo is BaseObjectWithID with extras, and fooRef uses foo, BaseObjWithId is a lesser version its not the same – BugFinder Apr 04 '17 at 11:17
  • 1
    Because it makes no sense what you are trying to do. You can cast it though, but it has no purpose. – Viezevingertjes Apr 04 '17 at 11:18
  • Since `FooRef` is tied to the instance of concrete class `Foo`, there's no way to tie it with its base class until and unless you make FooRef too generic, this is less about type conversion more about Generics – Mrinal Kamboj Apr 04 '17 at 11:23
  • Just because `Foo` inherits from `BaseObjWithId` it doesn't mean that `BaseReference` inherits from `BaseReference` - it doesn't - they are two different types. – Enigmativity Apr 04 '17 at 11:24
  • A `Cage` is a cage that can house any animal. An `Aquarium` is a `Cage`. So can you use a `Cage` where you need an `Aquarium`? No; an aquarium guarantees that only fish come out, but a `Cage` could release a tiger. Can you use an `Aquarium` where you need a `Cage`? No. You can put a giraffe into a `Cage` but not into an `Aquarium`. Since the conversion in both directions is impossible, there must be *no* type relationship between `Aquarium` and `Cage`. – Eric Lippert Apr 04 '17 at 12:04

2 Answers2

1

You are misunderstanding how generics works in relation to type inheritance. You can cast an object of type FooRef into a reference of type BaseReference<Foo> because FooRef inherits from BaseReference<Foo>. However, you cannot cast a BaseReference<Foo> into a BaseReference<BaseObjWithID> because unlike the types they are using as generic type arguments, those two generic classes have no such connection.

Take the following example:

public class Fruit {}
public class Apple : Fruit {}

Any Apple object can be stored in a Fruit reference because inheritance ensures that the relationship between them checks out:

Fruit f = new Apple();

However, where generics are involved, each time a generic version of a class is created with a different type argument, those versions are all treated as completely different types. For example, while the above implicit casting would work, the following would fail:

List<Fruit> f = new List<Apple>();

List<Fruit> and List<Apple> are completely different classes. There is no direct conversion between them, implicit or explicit.

Abion47
  • 22,211
  • 4
  • 65
  • 88
1

What are you searching for, called covariance. In your case - covariance in generics. According to this article it is only implemented for the limited set of types:

this works fine:

IEnumerable<Derived> b = new List<Derived>();
IEnumerable<Base> a = b;

this does not work:

IList<Derived> b = new List<Derived>();
IList<Base> a = b;

So, in your case, FooRef is directly derived from BaseReference<Foo>, so it can be casted to this type, while c# can not simply cast BaseReference<Foo> to BaseReference<BaseObjWithId> because of problems with generic arguments covariance.

There could be some workarounds, separated method with hardcoded conversion from one type to another, but, I guess, this behaviour should be avoided at all.

Community
  • 1
  • 1
Mikhail Tulubaev
  • 4,141
  • 19
  • 31