Condividi tramite


Uso della varianza nei delegati (C#)

Quando si assegna un metodo a un delegato, la covarianza e la controvarianza offrono flessibilità per la corrispondenza di un tipo delegato con una firma del metodo. Covarianza consente a un metodo di avere un tipo restituito più derivato di quello definito nel delegato. La controvarianza consente un metodo con tipi di parametro meno derivati rispetto a quelli nel tipo delegato.

Esempio 1: Covarianza

Descrizione

In questo esempio viene illustrato come usare i delegati con metodi che dispongono di tipi restituiti derivati dal tipo restituito nella firma del delegato. Il tipo di dati restituito da DogsHandler è di tipo Dogs, che deriva dal Mammals tipo definito nel delegato.

Codice

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

Esempio 2: Controvarianza

Descrizione

In questo esempio viene illustrato come usare i delegati con metodi con parametri i cui tipi sono tipi di base del tipo di parametro della firma del delegato. Con la controvarianza, è possibile usare un gestore di eventi anziché gestori separati. L'esempio seguente usa due delegati:

  • Delegato personalizzato KeyEventHandler che definisce la firma di un evento di chiave. La firma è:

    public delegate void KeyEventHandler(object sender, KeyEventArgs e)
    
  • Un delegato personalizzato MouseEventHandler che specifica la firma di un evento del mouse. La firma è:

    public delegate void MouseEventHandler(object sender, MouseEventArgs e)
    

L'esempio definisce un gestore eventi con un EventArgs parametro e lo usa per gestire sia gli eventi di chiave che di mouse. Ciò funziona perché EventArgs è un tipo di base delle classi personalizzate KeyEventArgs e MouseEventArgs definite nell'esempio. La controvarianza consente a un metodo che accetta un parametro di tipo di base da usare per gli eventi che forniscono parametri di tipo derivati.

Funzionamento della controvarianza in questo esempio

Quando si sottoscrive un evento, il compilatore verifica se il metodo del gestore eventi è compatibile con la firma del delegato dell'evento. Con controvarianza:

  1. L'evento KeyDown prevede un metodo che accetta KeyEventArgs.
  2. L'evento MouseClick prevede un metodo che accetta MouseEventArgs.
  3. Il metodo MultiHandler accetta il tipo base EventArgs.
  4. Poiché KeyEventArgs e MouseEventArgs ereditano da EventArgs, possono essere passati in modo sicuro a un metodo che prevede EventArgs.
  5. Il compilatore consente questa assegnazione perché è sicura: MultiHandler può funzionare con qualsiasi EventArgs istanza.

Questa è la controvarianza in azione: è possibile usare un metodo con un parametro "meno specifico" (tipo di base) in cui è previsto un parametro "più specifico" (tipo derivato).

Codice

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

Punti chiave sulla controvarianza

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

Quando si esegue questo esempio, si noterà che lo stesso metodo MultiHandler gestisce correttamente sia gli eventi della tastiera che quelli del mouse, dimostrando come la controvarianza consenta una gestione degli eventi più flessibile e riutilizzabile.

Vedere anche