Megosztás a következőn keresztül:


Variancia használata a delegátumokban (C#)

Amikor metódust rendel egy delegálthoz, a kovariancia és kontravariancia rugalmasságot biztosít a delegált típus illesztése a metódus aláírásához. A kovariancia lehetővé teszi, hogy egy metódus olyan visszatérési típussal rendelkezzen, amely származtatottabb, mint a delegáltban definiált. A contravariance olyan metódust engedélyez, amely a delegált típushoz képest kevésbé származtatott paramétertípusokkal rendelkezik.

1. példa: Kovariancia

Leírás

Ez a példa bemutatja, hogyan használhatók a delegátusok olyan metódusokkal, amelyek a delegátus aláírás visszatérési típusából származtatott visszatérési típussal rendelkeznek. A DogsHandler visszaadott adattípus típusa Dogs, ami a delegáltban definiált Mammals típusból származik.

Kód

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

2. példa: Kontravariancia

Leírás

Ez a példa bemutatja, hogyan használhatók a delegáltak olyan metódusokkal, amelyek olyan paraméterekkel rendelkeznek, amelyek típusa a delegált aláírás paramétertípus alaptípusa. A contravariance használatával külön kezelők helyett egy eseménykezelőt használhat. Az alábbi példa két meghatalmazottat használ:

  • Egy kulcsesemény aláírását meghatározó egyéni KeyEventHandler meghatalmazott. Aláírása:

    public delegate void KeyEventHandler(object sender, KeyEventArgs e)
    
  • Az egéresemény aláírását meghatározó egyéni MouseEventHandler delegátum. Aláírása:

    public delegate void MouseEventHandler(object sender, MouseEventArgs e)
    

A példa egy paraméterrel rendelkező EventArgs eseménykezelőt határoz meg, és a kulcs- és egéresemények kezelésére is használja. Ez azért működik, mert EventArgs a példában definiált egyéni KeyEventArgs és MouseEventArgs osztályok alaptípusa. A contravariance lehetővé teszi egy olyan metódus használatát, amely elfogadja az alaptípus-paramétert a származtatott típusparamétereket biztosító eseményekhez.

A contravariance működése ebben a példában

Amikor előfizet egy eseményre, a fordító ellenőrzi, hogy az eseménykezelő metódus kompatibilis-e az esemény delegált aláírásával. Kontravarianciával:

  1. Az KeyDown esemény egy olyan metódust vár, amely KeyEventArgs paramétert kap.
  2. Az MouseClick esemény egy olyan metódust vár, amely MouseEventArgs paramétert vár el.
  3. A MultiHandler metódus az alaptípust veszi fel EventArgs.
  4. Mivel a KeyEventArgs és a MouseEventArgs is a EventArgs-től öröklődnek, biztonságosan átadhatók egy EventArgs-et váró metódusnak.
  5. A fordító lehetővé teszi ezt a hozzárendelést, mert biztonságos – bármelyik MultiHandler példánnyal EventArgs dolgozhat.

Ez a működés közbeni ellentravariancia: olyan metódust is használhat, amely "kevésbé specifikus" (alaptípusú) paraméterrel rendelkezik, ott, ahol "specifikusabb" (származtatott típusú) paraméter várható.

Kód

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

A kontravariancia legfontosabb pontjai

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

A példa futtatásakor látni fogja, hogy ugyanez MultiHandler a módszer sikeresen kezeli a kulcs- és egéreseményeket is, bemutatva, hogy a contravariance hogyan teszi lehetővé a rugalmasabb és újrafelhasználhatóabb eseménykezelési kódot.

Lásd még