Grundsätzlich geht es bei Ko- und Kontravarianz um die Kompatibilität von Ein- und Rückgabewerten.
Durch Polymorphie kann - einfach ausgedrückt - ein Typ in verschiedener Gestalt auftreten.
Weiters sind Delegaten als Funktionszeiger zu verstehen die stark typisiert sind.
Da abgeleitete Typen auch Typen der jeweiligen Basisklasse sind (Polymorphie) können Delegaten die als Rückgabetyp den Basistyp erwarten auch auf Methoden zeigen die einen abgeleiteten Typ zurückgeben (Kovarianz) - dabei wird die Vererbungshierarchie nicht verletzt.
Delegaten die als Argument einen abgeleiteten Typ erwarten können auch auf Methoden zeigen welche als Argument den Basistyp erwarten (Kontravarianz) - auch hier wird die Vererbungshierarchie nicht verletzt.
Wie von dir richtig festgestellt ist Kovarianz mit der Vererbungsrichtung und Kontravarianz entgegen der Vererbungsrichtung.
Ich würde nicht allzu viel Wert auf korrekte Bezeichnungen legen sonder mehr auf das Verständnis "wie was" funktioniert. Die Bezeichnungen für das "wie was" sind meiner Meinung nach nur dazu da dass ein "Kind einen Namen hat" und derjenige der zum ersten Mal dies veröffentlicht mehr oder weniger Ruhm hat.
Aber genug der Worte. Folgend Beispiele in C# die den Sachverhalt klären versuchen.
Kovarianzusing System;
namespace Kovarianz_Kontravarianz
{
class Kovarianz
{
// Delegat für eine Methode die als Rückgabewert FooBase erwartet.
delegate FooBase Creator();
//---------------------------------------------------------------------
static void Main()
{
// Normale Verwendung des Delegaten:
Creator fooBaseCreator = CreateFooBase;
// Verwendung des Delegaten mit Kovarianz:
// Der tatsächliche Rückgabetyp der Methode Foo erbt vom Typen
// FooBase den der Delegat erwartet. Somit gilt:
// Typ: Foo -> FooBase
// Rückgabetyp: Foo -> FooBase
// Beide Vererbungsrichtungen sind in die selbe Richtung und
// somit kovariant.
Creator fooCreator = CreateFoo;
// Aufrufen der Delegaten:
FooBase object1 = fooBaseCreator();
FooBase object2 = fooCreator();
// Kontrollausgabe um zu sehen welche Typen erzeugt wurden:
Console.WriteLine(object1.GetType().Name);
Console.WriteLine(object2.GetType().Name);
Console.ReadKey();
}
//---------------------------------------------------------------------
// Erzeugt eine Basisklasse.
static FooBase CreateFooBase() { return new FooBase(); }
//---------------------------------------------------------------------
// Erzeugt einen von der Basisklasse abgeleiteten Typ.
static Foo CreateFoo() { return new Foo(); }
}
//-------------------------------------------------------------------------
class FooBase { }
//-------------------------------------------------------------------------
class Foo : FooBase { }
}
Kontravarianzusing System;
namespace Kovarianz_Kontravarianz
{
class Kovarianz
{
// Delegat für eine Methode mit einem Argument vom Typ FooBase.
delegate void DoSomethingWithFooBase(FooBase o);
// Delegat für eine Methode mit einem Argument vom Typ Foo.
delegate void DoSomethingWithFoo(Foo o);
//---------------------------------------------------------------------
static void Main()
{
// Normale Verwendung des Delegaten:
DoSomethingWithFooBase printTypeOfFooBase = PrintType;
// Verwendung des Delegaten mit Kontravarianz:
// Der von der Methode erwartete Typ des Arguments ist FooBase
// und der vom Delegaten deklarierte Typ ist Foo welcher von FooBase
// erbt. Somit gilt:
// Typ: Foo -> FooBase
// Argument-Typ: FooBase <- Foo
DoSomethingWithFoo printTypeOfFoo = PrintType;
// Aufrufen der Delegaten:
printTypeOfFooBase(new FooBase());
//printTypeOfFooBase(new Foo()); // ist auch möglich
printTypeOfFoo(new Foo());
Console.ReadKey();
}
//---------------------------------------------------------------------
// Gibt des Typ.
static void PrintType(FooBase o)
{
Console.WriteLine(o.GetType().Name);
}
}
//-------------------------------------------------------------------------
class FooBase { }
//-------------------------------------------------------------------------
class Foo : FooBase { }
}
Hoffe es kann dir helfen.