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.
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: