1

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.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
AbeMonk
  • 110
  • 7
  • `public abstract IEnumerable SomeStuff { get; }` defines a **new** "slot" for a new property which is a distinct property entirely separate from `public override IEnumerable SomeStuff { get; }` - you'll get a compiler warning about missing the `new` modifier for explicit shadowing. – Dai Aug 09 '23 at 02:09
  • @Dai If the abstract and overriden properties only have getters but **not** setters, then it compiles with no issues. It's only a problem when I add the setters, which is what I don't understand. – AbeMonk Aug 09 '23 at 02:13
  • 1
    The answer is here: https://stackoverflow.com/questions/65230700/c-sharp-9-0-covariant-return-types-and-interfaces - C# 9.0 introduced return-type variance for virtual methods and properties in `class` types - but not `interface` types. – Dai Aug 09 '23 at 02:21
  • 2
    Remember, if it worked the way you wanted, with that setter on the base class, `StuffBase b = new StuffDerived(); b.SomeStuff = new AnythingThatImplementsIEnumerable();` would compile but that derived class isn't anticipating anything other that `IEnumerable` derived classes. – Damien_The_Unbeliever Aug 09 '23 at 05:44
  • @Damien_The_Unbeliever Ah, that actually makes sense. – AbeMonk Aug 09 '23 at 11:36

1 Answers1

1

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:

Guru Stron
  • 102,774
  • 10
  • 95
  • 132