Does anyone know why covariant return types are not supported in C#? Even when attempting to use an interface, the compiler complains that it is not allowed. See the following example.
class Order
{
private Guid? _id;
private String _productName;
private double _price;
protected Order(Guid? id, String productName, double price)
{
_id = id;
_productName = productName;
_price = price;
}
protected class Builder : IBuilder<Order>
{
public Guid? Id { get; set; }
public String ProductName { get; set; }
public double Price { get; set; }
public virtual Order Build()
{
if(Id == null || ProductName == null || Price == null)
throw new InvalidOperationException("Missing required data!");
return new Order(Id, ProductName, Price);
}
}
}
class PastryOrder : Order
{
PastryOrder(Guid? id, String productName, double price, PastryType pastryType) : base(id, productName, price)
{
}
class PastryBuilder : Builder
{
public PastryType PastryType {get; set;}
public override PastryOrder Build()
{
if(PastryType == null) throw new InvalidOperationException("Missing data!");
return new PastryOrder(Id, ProductName, Price, PastryType);
}
}
}
interface IBuilder<in T>
{
T Build();
}
public enum PastryType
{
Cake,
Donut,
Cookie
}
Thanks for any responses.
UPDATE: This answer was written in 2011. After two decades of people proposing return type covariance for C#, it looks like it will finally be implemented; I am rather surprised. See the bottom of https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ for the announcement; I'm sure details will follow.
First off, return type contravariance doesn't make any sense; I think you are talking about return type covariance.
See this question for details:
Does C# support return type covariance?
You want to know why the feature is not implemented. phoog is correct; the feature is not implemented because no one here ever implemented it. A necessary but insufficient requirement is that the feature's benefits exceed its costs.
The costs are considerable. The feature is not supported natively by the runtime, it works directly against our goal to make C# versionable because it introduces yet another form of the brittle base class problem, Anders doesn't think it is an interesting or useful feature, and if you really want it, you can make it work by writing little helper methods. (Which is exactly what the CIL version of C++ does.)
The benefits are small.
High cost, small benefit features with an easy workaround get triaged away very quickly. We have far higher priorities.
The contravariant generic parameter cannot be output, because that cannot be guaranteed to be safe at compile time, and C# designers made a decision not to prolong the necessary checks to the run-time.
This is the short answer, and here is a slightly longer one...
Variance is a property of a transformation applied to a type hierarchy:
In C#, the "transformation" is "being used as a generic parameter". For example, let's say a class Parent
is inherited by class Child
. Let's denote that fact as: Parent
> Child
(because all Child
instances are also Parent
instances, but not necessarily the other way around, hence Parent
is "bigger"). Let's also say we have a generic interface I<T>
:
I<Parent>
> I<Child>
, the T is covariant (the original "direction" between Parent
and Child
is kept).I<Parent>
< I<Child>
, the T is contravariant (the original "direction" is reversed).I<Parent>
is unrelated to I<Child>
, the T is invariant.If C# compiler actually agreed to compile the following code...
class Parent {
}
class Child : Parent {
}
interface I<in T> {
T Get(); // Imagine this actually compiles.
}
class G<T> : I<T> where T : new() {
public T Get() {
return new T();
}
}
// ...
I<Child> g = new G<Parent>(); // OK since T is declared as contravariant, thus "reversing" the type hierarchy, as explained above.
Child child = g.Get(); // Yuck!
...this would lead to a problem at run-time: a Parent
is instantiated and assigned to a reference to Child
. Since Parent
is not Child
, this is wrong!
The last line looks OK at compile-time since I<Child>.Get
is declared to return Child
, yet we could not fully "trust" it at run-time. C# designers decided to do the right thing and catch the problem completely at compile-time, and avoid any need for the run-time checks (unlike for arrays).
(For similar but "reverse" reasons, covariant generic parameter cannot be used as input.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With