Freigeben über


Verwenden von Varianz bei Delegaten (C#)

Wenn Sie einer Delegaten eine Methode zuweisen, bieten Kovarianz und Kontravarianz Flexibilität bei der Anpassung eines Delegattyps an eine Methodensignatur. Kovarianz lässt die Verfügung einer Methode über einen Rückgabetyp zu, der stärker abgeleitet ist als der im Delegat definierte Typ. Contravarianz erlaubt eine Methode mit Parametertypen, die weniger abgeleitet sind als diejenigen im Delegatentyp.

Beispiel 1: Kovarianz

BESCHREIBUNG

In diesem Beispiel wird veranschaulicht, wie Delegaten mit Methoden verwendet werden können, die über Rückgabetypen verfügen, die von den Rückgabetypen in der Delegatsignatur abgeleitet sind. Der von DogsHandler zurückgegebene Datentyp ist vom Typ Dogs, der von dem Typ Mammals abgeleitet ist, der im Delegaten definiert ist.

Code

class Mammals {}  
class Dogs : Mammals {}  
  
class Program  
{  
    // Define the delegate.  
    public delegate Mammals HandlerMethod();  
  
    public static Mammals MammalsHandler()  
    {  
        return null;  
    }  
  
    public static Dogs DogsHandler()  
    {  
        return null;  
    }  
  
    static void Test()  
    {  
        HandlerMethod handlerMammals = MammalsHandler;  
  
        // Covariance enables this assignment.  
        HandlerMethod handlerDogs = DogsHandler;  
    }  
}  

Beispiel 2: Kontravarianz

BESCHREIBUNG

In diesem Beispiel wird veranschaulicht, wie Stellvertretungen mit Methoden verwendet werden können, deren Typen Basistypen des Stellvertretungssignaturparametertyps sind. Mit kontravarianz können Sie einen Ereignishandler anstelle separater Handler verwenden. Im folgenden Beispiel werden zwei Stellvertretungen verwendet:

  • Ein angepasster KeyEventHandler Delegat, der die Signatur eines wichtigen Ereignisses definiert. Die Signatur lautet:

    public delegate void KeyEventHandler(object sender, KeyEventArgs e)
    
  • Ein benutzerdefinierter MouseEventHandler Delegat, der die Signatur eines Maus-Ereignisses definiert. Die Signatur lautet:

    public delegate void MouseEventHandler(object sender, MouseEventArgs e)
    

Im Beispiel wird ein Ereignishandler mit einem EventArgs Parameter definiert und zum Behandeln von Tasten- und Mausereignissen verwendet. Dies funktioniert, weil EventArgs ein Basistyp sowohl für die benutzerdefinierte KeyEventArgs-Klasse als auch die MouseEventArgs-Klasse ist, die im Beispiel definiert sind. Contravariance ermöglicht eine Methode, die einen Basistypparameter für Ereignisse akzeptiert, die abgeleitete Typparameter bereitstellen.

Funktionsweise der Kontravarianz in diesem Beispiel

Wenn Sie ein Ereignis abonnieren, überprüft der Compiler, ob die Ereignishandlermethode mit der Stellvertretungssignatur des Ereignisses kompatibel ist. Mit Kontravarianz:

  1. Das KeyDown-Ereignis erwartet eine Methode, die KeyEventArgs annimmt.
  2. Das MouseClick-Ereignis erwartet eine Methode, die MouseEventArgs nimmt.
  3. Ihre MultiHandler Methode verwendet den Basistyp EventArgs.
  4. Da KeyEventArgs und MouseEventArgs beide erben von EventArgs, können sie sicher an eine Methode übergeben werden, die EventArgserwartet .
  5. Der Compiler lässt diese Zuweisung zu, da sie sicher ist – dies MultiHandler kann mit jeder EventArgs Instanz funktionieren.

Dies ist eine Kontravarianz in Aktion: Sie können eine Methode mit einem "weniger spezifischen" (Basistyp)-Parameter verwenden, bei dem ein "spezifischerer" (abgeleiteter Typ)-Parameter erwartet wird.

Code

// Custom EventArgs classes to demonstrate the hierarchy
public class KeyEventArgs(string keyCode) : EventArgs
{
    public string KeyCode { get; set; } = keyCode;
}

public class MouseEventArgs(int x, int y) : EventArgs  
{
    public int X { get; set; } = x;
    public int Y { get; set; } = y;
}

// Define delegate types that match the Windows Forms pattern
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public delegate void MouseEventHandler(object sender, MouseEventArgs e);

// A simple class that demonstrates contravariance with events
public class Button
{
    // Events that expect specific EventArgs-derived types
    public event KeyEventHandler? KeyDown;
    public event MouseEventHandler? MouseClick;

    // Method to simulate key press
    public void SimulateKeyPress(string key)
    {
        Console.WriteLine($"Simulating key press: {key}");
        KeyDown?.Invoke(this, new KeyEventArgs(key));
    }

    // Method to simulate mouse click  
    public void SimulateMouseClick(int x, int y)
    {
        Console.WriteLine($"Simulating mouse click at ({x}, {y})");
        MouseClick?.Invoke(this, new MouseEventArgs(x, y));
    }
}

public class Form1
{
    private Button button1;

    public Form1()
    {
        button1 = new Button();
        
        // Event handler that accepts a parameter of the base EventArgs type.
        // This method can handle events that expect more specific EventArgs-derived types
        // due to contravariance in delegate parameters.
        
        // You can use a method that has an EventArgs parameter,
        // although the KeyDown event expects the KeyEventArgs parameter.
        button1.KeyDown += MultiHandler;

        // You can use the same method for an event that expects 
        // the MouseEventArgs parameter.
        button1.MouseClick += MultiHandler;
    }

    // Event handler that accepts a parameter of the base EventArgs type.
    // This works for both KeyDown and MouseClick events because:
    // - KeyDown expects KeyEventHandler(object sender, KeyEventArgs e)
    // - MouseClick expects MouseEventHandler(object sender, MouseEventArgs e)  
    // - Both KeyEventArgs and MouseEventArgs derive from EventArgs
    // - Contravariance allows a method with a base type parameter (EventArgs)
    //   to be used where a derived type parameter is expected
    private void MultiHandler(object sender, EventArgs e)
    {
        Console.WriteLine($"MultiHandler called at: {DateTime.Now:HH:mm:ss.fff}");
        
        // You can check the actual type of the event args if needed
        switch (e)
        {
            case KeyEventArgs keyArgs:
                Console.WriteLine($"  - Key event: {keyArgs.KeyCode}");
                break;
            case MouseEventArgs mouseArgs:
                Console.WriteLine($"  - Mouse event: ({mouseArgs.X}, {mouseArgs.Y})");
                break;
            default:
                Console.WriteLine($"  - Generic event: {e.GetType().Name}");
                break;
        }
    }

    public void DemonstrateEvents()
    {
        Console.WriteLine("Demonstrating contravariance in event handlers:");
        Console.WriteLine("Same MultiHandler method handles both events!\n");
        
        button1.SimulateKeyPress("Enter");
        button1.SimulateMouseClick(100, 200);
        button1.SimulateKeyPress("Escape");
        button1.SimulateMouseClick(50, 75);
    }
}

Wichtige Punkte zur Kontravarianz

// Demonstration of how contravariance works with delegates:
// 
// 1. KeyDown event signature: KeyEventHandler(object sender, KeyEventArgs e)
//    where KeyEventArgs derives from EventArgs
//
// 2. MouseClick event signature: MouseEventHandler(object sender, MouseEventArgs e)  
//    where MouseEventArgs derives from EventArgs
//
// 3. Our MultiHandler method signature: MultiHandler(object sender, EventArgs e)
//
// 4. Contravariance allows us to use MultiHandler (which expects EventArgs)
//    for events that provide more specific types (KeyEventArgs, MouseEventArgs)
//    because the more specific types can be safely treated as their base type.
//
// This is safe because:
// - The MultiHandler only uses members available on the base EventArgs type
// - KeyEventArgs and MouseEventArgs can be implicitly converted to EventArgs
// - The compiler knows that any EventArgs-derived type can be passed safely

Wenn Sie dieses Beispiel ausführen, sehen Sie, dass die gleiche MultiHandler Methode sowohl Tasten- als auch Mausereignisse erfolgreich behandelt und veranschaulicht, wie contravariance flexibleren und wiederverwendbaren Ereignisbehandlungscode ermöglicht.

Siehe auch