Dela via


Använda varians i delegater (C#)

När du tilldelar en metod till ett ombud ger samvarians och kontravarians flexibilitet för att matcha en delegattyp med en metodsignatur. Med kovarians kan en metod ha en returtyp som är mer härledd än den som definierats i ombudet. Contravarians tillåter en metod som har parametertyper som är mindre specifika än de i delegattypen.

Exempel 1: Kovarians

Beskrivning

Det här exemplet visar hur ombud kan användas med metoder som har returtyper som härleds från returtypen i delegatsignaturen. Datatypen som returneras av DogsHandler är av typen Dogs, som härleds från den Mammals typ som definieras i ombudet.

Kod

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;  
    }  
}  

Exempel 2: Kontravarians

Beskrivning

Det här exemplet visar hur ombud kan användas med metoder som har parametrar vars typer är bastyper av parametertypen ombudssignatur. Med kontravarians kan du använda en händelsehanterare i stället för separata hanterare. I följande exempel används två ombud:

  • Ett anpassat KeyEventHandler ombud som definierar signaturen för en nyckelhändelse. Dess signatur är:

    public delegate void KeyEventHandler(object sender, KeyEventArgs e)
    
  • Ett anpassat MouseEventHandler delegate som definierar signaturen för en mushändelse. Dess signatur är:

    public delegate void MouseEventHandler(object sender, MouseEventArgs e)
    

Exemplet definierar en händelsehanterare med en EventArgs parameter och använder den för att hantera både nyckel- och mushändelser. Detta fungerar eftersom EventArgs är en bastyp för både anpassade KeyEventArgs klasser och MouseEventArgs klasser som definieras i exemplet. Med contravariance kan en metod som accepterar en bastypparameter användas för händelser som tillhandahåller parametrar av härledd typ.

Hur fungerar contravariance i det här exemplet

När du prenumererar på en händelse kontrollerar kompilatorn om händelsehanterarmetoden är kompatibel med händelsens ombudssignatur. Med kontravarians:

  1. Händelsen KeyDown förväntar sig en metod som tar KeyEventArgs.
  2. Händelsen MouseClick förväntar sig en metod som tar MouseEventArgs.
  3. Metoden MultiHandler antar bastypen EventArgs.
  4. Eftersom KeyEventArgs och MouseEventArgs båda ärver från EventArgskan de skickas säkert till en metod som förväntar sig EventArgs.
  5. Kompilatorn tillåter den här tilldelningen MultiHandler eftersom den är säker – kan fungera med valfri EventArgs instans.

Detta är kontravarians i praktiken: du kan använda en metod med en "mindre specifik" parameter (bastyp) där en "mer specifik" parameter (härledd typ) förväntas.

Kod

// 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);
    }
}

Viktiga punkter om kontravarians

// 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

När du kör det här exemplet ser du att samma MultiHandler metod hanterar både nyckel- och mushändelser, vilket visar hur kontravarians möjliggör mer flexibel och återanvändbar kod för händelsehantering.

Se även