Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic covariance with interfaces - Weird behavioural contradiction between "is" and "=" operators

I had a complete wtf moment when dealing with covariant interfaces.

Consider the following:

class Fruit { }
class Apple : Fruit { }

interface IBasket<out T> { }

class FruitBasket : IBasket<Fruit> { }
class AppleBasket : IBasket<Apple> { }

Note:

  • AppleBasket does not inherit from FruitBasket.
  • IBasket is covariant.

Later on in the script, you write:

FruitBasket fruitBasket = new FruitBasket();
AppleBasket appleBasket = new AppleBasket();

Log(fruitBasket is IBasket<Fruit>);
Log(appleBasket is IBasket<Apple>);

...and as you'd expect, the output is:

true
true

HOWEVER, consider the following code:

AppleBasket appleBasket = new AppleBasket();

Log(appleBasket is IBasket<Fruit>);    

You'd expect it to output true, right? Well, you are wrong -- at least with my compiler anyway:

false    

That's strange, but perhaps it's performing an implicit conversion, similar to converting an int to a long. An int is not a kind of long, but a long can be assigned the value of an int implicitly.

HOWEVER, consider this code:

IBasket<Fruit> basket = new AppleBasket(); // implicit conversion?

Log(basket is IBasket<Fruit>);

This code runs just fine - no compiler errors or exceptions - even though we previously learned that an AppleBasket is NOT a kind of IBasket<Fruit>. Unless there is some third option, it must be doing an implicit conversion in the assignment.

Surely basket - declared as an IBasket<Fruit> - MUST be an instance of IBasket<Fruit>... I mean, that's what it was declared as. Right?

But no, according to the is operator, you're wrong again! It outputs:

false

...Meaning IBasket<Fruit> fruit is NOT an instance of IBasket<Fruit>... Huh?

...Meaning the following property:

IBasket<Fruit> FruitBasket { get { ... } }

Can sometimes return something that both isn't null, and isn't an instance of IBasket<Fruit>.


FURTHER, ReSharper tells me that appleBasket is IBasket<Fruit> is redundant in that appleBasket is always of the provided type, and can be safely replaced with appleBasket != null... ReSharper got it wrong too?

So, what's going on here? Is this just my version of C# (Unity 5.3.1p4 - It's Unity's own branch of Mono, based off of .NET 2.0) being a nut?

like image 775
Hatchling Avatar asked Nov 08 '22 16:11

Hatchling


1 Answers

Based on your comments, it is no small surprise that covariance isn't properly being supported...it was added in C#4.

What IS incredibly surprising is that it is even compiling at all if it is targeting a port based off of .NET 2.0

like image 133
David L Avatar answered Nov 14 '22 21:11

David L