2

Let's consider that there is an abstract base class and one, or more child classes:

public abstract class BaseInnerClass
{
    public int Id { get; set; }
}

public class ConcreteInnerClass : BaseInnerClass
{
    public string Name { get; set; }
}

Then, let's assume there is a generic abstract class that has a property of above abstract class type:

public abstract class GeneriAbstractTestClass<T> where T : BaseInnerClass
{
    public T InnerClass { get; set; }    
}

Then let's make a class that inherits from the class above:

public class ConcreteTestClass : GeneriAbstractTestClass<ConcreteInnerClass>
{
    public string ConcreteString { get; set; }
}

So now everything is prepared to ask a question ;) Why it is not possible to do it:

 //cannot convert initializer type
 GeneriAbstractTestClass<BaseInnerClass> genericClass = new ConcreteTestClass();

while this is allowed:

 //ok
 BaseInnerClass baseInner = new ConcreteInnerClass();

What's the difference between this two assignments?

Adam Stepniak
  • 815
  • 6
  • 21
  • 5
    Simply put, a `GeneriAbstractTestClass` isn't a `GeneriAbstractTestClass` or vice versa, just like a `List` isn't a `List`. See https://stackoverflow.com/questions/7643211/how-to-pass-listderivedclass-when-param-type-is-listbaseclass/7643257#7643257 - not sure whether this *quite* counts as a duplicate, but it's very close. – Jon Skeet Aug 10 '17 at 20:13
  • I think it will helpful for you to read it: [real world example](https://stackoverflow.com/questions/2662369/covariance) and [covariance-contravariance](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/) – George Alexandria Aug 10 '17 at 20:13

2 Answers2

7

This has nothing to do with abstract classes. A simpler example would be

List<BaseInnerClass> base = new List<ConcreteInnerClass>

The fact that type A is derived from type B does not imply that type C<A> is derived from type C<B>. Your example is a little bit more complicated, but it can be explained using the same logic.

Note that you can define another concrete type:

public class EvilConcreteInnerClass : BaseInnerClass
{
} 

If what you wanted was possible, then the following would work:

GeneriAbstractTestClass<BaseInnerClass> genericClass = new ConcreteTestClass();
genericClass.InnerClass = new EvilConcreteInnerClass(); // OK, because the compiler sees `T` as `BaseInnerClass`

genericClass variable points to an object whose T generic parameter is ConcreteInnerClass, so assigning EvilConcreteInnerClass to the property would result in a run-time exception.

Kapol
  • 6,383
  • 3
  • 21
  • 46
0

Actually. You can do this. But you need to specify interface with covariant out T generic, because it is type safe to make those casts.

Example

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var a = new Generic<Concrete>();
            IGeneric<Base> c = new Generic<Base>();
            c = a;
        }
    }

    public interface IGeneric<out T> where T: Base
    {
        T Inner { get; }
    }

    public class Generic<T> : IGeneric<T>
        where T : Base
    {
        public T Inner { get; set; }
    }

    public class Concrete : Base
    {
    }

    public class Base
    {
    }
}

Delegates also not restricted if they specify covariant out generic templates.

It means those casts you want is OK as long as you use readonly generic properties. So, like @Kapol said and provided you example why it is not type safe to allow setters on properties or pass T into function.

Summary

Use ReadOnly interfaces if you want to use those kinds of casts.

eocron
  • 6,885
  • 1
  • 21
  • 50