Partager via


Utilisation de la variance dans les délégués (C#)

Lorsque vous affectez une méthode à un délégué, la covariance et la contravariance offrent une flexibilité pour la mise en correspondance d’un type délégué avec une signature de méthode. La covariance permet à une méthode d’avoir un type de retour qui est plus dérivé que celui défini dans le délégué. La contravariance permet une méthode qui a des types de paramètres qui sont moins dérivés que ceux du type délégué.

Exemple 1 : Covariance

Descriptif

Cet exemple montre comment les délégués peuvent être utilisés avec des méthodes qui ont des types de retour dérivés du type de retour dans la signature de délégué. Le type de données retourné par DogsHandler est de type Dogs, qui dérive du Mammals type défini dans le délégué.

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

Exemple 2 : Contravariance

Descriptif

Cet exemple montre comment les délégués peuvent être utilisés avec des méthodes qui ont des paramètres dont les types sont des types de base du type de paramètre de signature de délégué. Avec la contravariance, vous pouvez maintenant utiliser un gestionnaire d’événements plutôt que des gestionnaires distincts. L’exemple suivant utilise deux délégués :

  • Délégué personnalisé KeyEventHandler qui définit la signature d’un événement clé. Sa signature est :

    public delegate void KeyEventHandler(object sender, KeyEventArgs e)
    
  • Un délégué MouseEventHandler personnalisé qui définit la signature d’un événement de souris. Sa signature est :

    public delegate void MouseEventHandler(object sender, MouseEventArgs e)
    

L’exemple définit un gestionnaire d’événements avec un EventArgs paramètre et l’utilise pour gérer les événements de touche et de souris. Cela fonctionne, car EventArgs il s’agit d’un type de base des classes personnalisées KeyEventArgs et MouseEventArgs définies dans l’exemple. La contravariance permet à une méthode qui accepte un paramètre de type de base d'être utilisée pour des événements qui fournissent des paramètres de type dérivé.

Fonctionnement de la contravariance dans cet exemple

Lorsque vous vous abonnez à un événement, le compilateur vérifie si votre méthode de gestionnaire d’événements est compatible avec la signature déléguée de l’événement. Avec contravariance :

  1. L’événement KeyDown attend une méthode qui prend KeyEventArgs.
  2. L’événement MouseClick attend une méthode qui prend MouseEventArgs.
  3. La méthode MultiHandler prend le type de base EventArgs.
  4. Étant donné que KeyEventArgs et MouseEventArgs héritent tous de EventArgs, ils peuvent être transmis en toute sécurité à une méthode attendant EventArgs.
  5. Le compilateur autorise cette affectation, car elle est sécurisée : elle MultiHandler peut fonctionner avec n’importe quelle EventArgs instance.

Il s’agit de contravariance en action : vous pouvez utiliser une méthode avec un paramètre « moins spécifique » (type de base) où un paramètre « plus spécifique » (type dérivé) est attendu.

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

Points clés sur la contravariance

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

Lorsque vous exécutez cet exemple, vous verrez que la même méthode MultiHandler gère correctement les événements clavier et souris, ce qui montre comment la contravariance permet un code de gestion des événements plus flexible et réutilisable.

Voir aussi