4

I'm a Java developer and also learning about C# and .Net. Recently I've learned that there are upper bounded and lower bounded wildcards in Java.

<? extends AbstractClass> // upper bounded wildcard
<? super ConcreteClass>   // lower bounded wildcard

I tried googling the answer, but I only read about variancy, which confused me even more.

Would it be possible to do something like this in C#?:

public static void addCat(List<? super Cat> list) {
    list.add(new Cat());
}

// ...

interface Pet {}
class Cat implements Pet {}
class Dog implements Pet {}

// ...

public static void main(String[] args) {
    List<Pet> pets = new ArrayList<>();
    pets.add(new Dog());
    addCat(pets);
}
jhamon
  • 3,603
  • 4
  • 26
  • 37
G_hi3
  • 588
  • 5
  • 22
  • In C# we can do it. As `Dog` and `Cat` class implementing same interface and you are creating interface of `Pet`(IPet is proper name for interface) list it will add instance of both the classes which implement same interface. – Prasad Telkikar Aug 28 '19 at 10:09
  • You already know at code writing time that `super Cat` is `Pet`, don't you? I don't see a benefit of writing `List` over `List` – Thomas Weller Aug 28 '19 at 10:12
  • Possibly related: [C# generics - without lower bounds by design?](https://stackoverflow.com/questions/1993494/) and [How is Generic Covariance & Contra-variance Implemented in C# 4.0?](https://stackoverflow.com/questions/245607/). – Slaw Aug 28 '19 at 10:37
  • @ThomasWeller sorry, I forgot in my learning example, `Cat` had implemented two interfaces. What I want to tell the caller is that I want a `List` that can contain `Cat`, but I won't restrict the list to _only_ contain `Cat` objects. – G_hi3 Aug 28 '19 at 10:49

1 Answers1

4

C# also supports variance (co- and contravariance) which is closely related to the "upper/lower" generic bounds in Java.

However, it is not possible to make certain class variant. In fact, only generic interfaces and generic delegate types support variance.

In order to mark certain interface as covariant (i.e. to enable to use more derived type than original), you need to use out keyword next to its generic type parameter(s), as shown in the example:

interface IPetHouse<out T> where T : IPet
{
    T Pet { get; }
};

class CatHouse : IPetHouse<Cat>
{
    public Cat Pet => new Cat();
}

class DogHouse : IPetHouse<Dog>
{
    public Dog Pet => new Dog();
}

// 'out' keyword gives possibility to treat CatHouse and DogHouse as IPetHouse<IPet> since IPet is less derived type
var petHouses = new IPetHouse<IPet>[] {
    new CatHouse(), 
    new DogHouse()
};

// Types are preserved, which you can check easily 
petHouses.Select(ph => ph.Pet.GetType().Name); //outputs: Cat and Dog 

On the other hand, to mark interface as contravariant (i.e. to enable to use a more generic (less derived) type than originally specified), you need to use in keyword next to its generic type parameter(s), as shown in the example:

interface IPetHouse<in T> where T : IPet
{
    void AddPet(T pet);
};

class PetHouse : IPetHouse<IPet>
{
    public void AddPet(IPet pet)
    {
    }
}

class CatHouse : IPetHouse<Cat>
{
    public void AddPet(Cat pet)
    {
    }
}

// 'in' keyword gives possibility to treat PetHouse as a CatHouse, since Cat is more derived type
IPetHouse<IPet> petHouse = new PetHouse();
IPetHouse<Cat> catHouse = petHouse;
catHouse.AddPet(new Cat()); // requires Cat to be passed

More info can be found on the official MSDN page

Darjan Bogdan
  • 3,780
  • 1
  • 22
  • 31