Search code examples
c#classinheritanceabstract-class

Abstract property that can accept a derived type in the overridden property


Let's say I have an abstract class called StuffBase with an abstract property of type IEnumerable:

abstract class StuffBase
{
    public abstract IEnumerable SomeStuff { get; set; }
}

Now, is it possible to define a derived class that overrides the SomeStuff property with a derived type, like, for instance:

class StuffDerived : StuffBase
{
    public override IEnumerable<int> SomeStuff { get; set; }
}

The idea is that IEnumerable<int> is derived from IEnumerable. Is there any way to achieve something like this? When I currently try this, it gives me the error "StuffDerived does not implement abstract member StuffBase.SomeStuff.Set".

But here's what I don't quite get: If the abstract class only defines a getter, but not a setter, then it works. For instance, if SomeStuff is defined as

public abstract IEnumerable SomeStuff { get; }

and

public override IEnumerable<int> SomeStuff { get; }

it works perfectly fine. An explanation for this would also be nice.


Solution

  • The feature is called covariant return types and was introduced in C# 9:

    Support covariant return types. Specifically, permit the override of a method to declare a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to declare a more derived type. Override declarations appearing in more derived types would be required to provide a return type at least as specific as that appearing in overrides in its base types. Callers of the method or property would statically receive the more refined return type from an invocation.

    Note that if the setter would work like you have desired it would break the class contract. I.e. consider the following:

    var foo =  new StuffDerived()
    StuffBase bar = foo;
    bar.SomeStuff = new List<object> {"hahah"};
    int i = foo.First(); // boom
    

    You can use generics to "workaround" to some extent the setter problem:

    abstract class StuffBase<T>
    {
        public abstract IEnumerable<T> SomeStuff { get; set; }
    }
    
    class StuffDerived : StuffBase<int>
    {
        public override IEnumerable<int> SomeStuff { get; set; }
    }
    

    But it has some limitations like StuffDerived is not StuffBased<object> (i.e. StuffBase<object> foo = new StuffDerived(); will not compile). You can workaround a bit more with some base interface and explicit interface implementation, but again with no setter:

    interface IStuff
    {
        IEnumerable SomeStuff { get; }
    }
    abstract class StuffBase<T> : IStuff
    {
        IEnumerable IStuff.SomeStuff => SomeStuff;
        public abstract IEnumerable<T> SomeStuff { get; set; }
    }
    

    Actually some similar "problems" a relevant to "ordinary" variance in C# so you can read also: