Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics & Inheritance: What am I doing wrong here?

There are the following 4 objects declared:

abstract class AConfigAction {}

abstract class APlugin<ConfigActionType> where ConfigActionType :AConfigAction {}

class AppExecuteConfigAction : AConfigAction {}

class AppExecutePlugin : APlugin<AppExecuteConfigAction>{}
  • All classes are public. Bodies have been removed for simplicity.

Why this fails to be converted?

_plugins = new List<APlugin<AConfigAction>>();
_plugins.Add(new AppExecutePlugin());  <--- Error

cannot convert from 'AppExecutePlugin' to 'APlugin'


Full error message:

Error 1 The best overloaded method match for 'System.Collections.Generic.List>.Add(EnvironmentSwitcher.Model.ConfigAction.APlugin)' has some invalid arguments R:\projects\EnvironmentSwitcher\EnvironmentSwitcher\View\ConfigurationActionManagerForm.cs 35

Error 2 Argument '1': cannot convert from 'EnvironmentSwitcher.Model.ConfigAction.AppExecute.AppExecutePlugin' to 'EnvironmentSwitcher.Model.ConfigAction.APlugin' R:\projects\EnvironmentSwitcher\EnvironmentSwitcher\View\ConfigurationActionManagerForm.cs 35

like image 996
Odys Avatar asked Nov 28 '22 22:11

Odys


1 Answers

Let's make that a bit easier to understand:

abstract class Animal {} // was AConfigAction
abstract class Cage<T> where T : Animal {} // was APlugIn
class Tiger : Animal {} // was AppExecuteConfigAction
class TigerCage : Cage<Tiger>{} // was AppExecutePlugin

var cages = new List<Cage<Animal>>();    
cages.Add(new TigerCage()); // Why is this an error?

Suppose that were legal. What stops this?

class Shark : Animal {} // some other config action
...
var cages = new List<Cage<Animal>>();    
cages.Add(new TigerCage()); 
Cage<Animal> firstCage = cages[0]; 
firstCage.InsertIntoCage(new Shark());

firstCage is of type Cage<Animal> which implies that it can hold any kind of animal. But in fact we know that it is a tigers-only cage. You just put a shark into a tiger cage, which seems uncomfortable for both the shark and the tiger.

Obviously that cannot be allowed. What prevents it? The only thing that prevents it is that it is illegal to put a tiger cage into a collection of animal cages in the first place. A tiger cage is not a kind of animal cage because there are things you can do with an animal cage that you cannot do with a tiger cage, namely, put a shark into it. A basic principle of object-oriented design is that subtypes can do everything their supertypes can do; a tiger cage cannot do everything an animal cage can do, so it is not a subtype.

The more highfalutin way to say this is that generic types cannot be made to be covariant in their type arguments because doing so would violate the Liskov Substitution Principle. In C# 4, certain interfaces and delegates are covariant in their type arguments. For example, it is legal in C# 4 to put an IEnumerable<Tiger> into a List<IEnumerable<Animal>>> because there's no way that this can be made unsafe. We can uphold the substitution principle while allowing covariance because IEnumerable<T> is an "out-only" interface. You only ever take tigers out; there's no way to put sharks in.

like image 107
Eric Lippert Avatar answered Dec 10 '22 14:12

Eric Lippert