Bagikan melalui


Menggunakan Varians dalam Delegasi (C#)

Saat Anda menetapkan metode ke delegasi, kovariansi dan kontravarian memberikan fleksibilitas untuk mencocokkan jenis delegasi dengan tanda tangan metode. Kovarian memungkinkan metode untuk memiliki jenis pengembalian yang lebih khusus daripada yang ditentukan dalam delegasi. Kontravarian memungkinkan metode yang memiliki jenis parameter yang kurang diturunkan daripada yang ada dalam jenis delegasi.

Contoh 1: Kovarians

Deskripsi

Contoh ini menunjukkan bagaimana delegate dapat digunakan dengan metode yang memiliki tipe pengembalian yang diturunkan dari tipe pengembalian dalam tanda tangan delegate. Jenis data yang dikembalikan oleh DogsHandler adalah dari jenis Dogs, yang diturunkan dari jenis Mammals yang ditentukan dalam deleget.

Kode

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

Contoh 2: Kontravariansi

Deskripsi

Contoh ini menunjukkan bagaimana delegasi dapat digunakan dengan metode yang memiliki parameter yang jenisnya adalah jenis dasar dari jenis parameter tanda tangan delegasi. Dengan kontravariansi, Anda dapat menggunakan satu penanganan aktivitas alih-alih handler terpisah. Contoh berikut menggunakan dua delegasi:

  • Delegasi kustom KeyEventHandler yang menentukan tanda tangan peristiwa kunci. Tanda tangannya adalah:

    public delegate void KeyEventHandler(object sender, KeyEventArgs e)
    
  • Delegasi kustom MouseEventHandler yang menentukan tanda tangan peristiwa mouse. Tanda tangannya adalah:

    public delegate void MouseEventHandler(object sender, MouseEventArgs e)
    

Contoh mendefinisikan penanganan aktivitas dengan EventArgs parameter dan menggunakannya untuk menangani peristiwa kunci dan mouse. Ini berfungsi karena EventArgs merupakan jenis dasar dari kustom KeyEventArgs dan MouseEventArgs kelas yang ditentukan dalam contoh. Kontravariansi memungkinkan metode yang menerima parameter jenis dasar untuk digunakan untuk peristiwa yang menyediakan parameter jenis turunan.

Cara kerja kontravarian dalam contoh ini

Saat Anda berlangganan event, pengkompilasi memeriksa apakah metode penanganan event Anda kompatibel dengan signature delegasi dari event tersebut. Dengan kontravariansi:

  1. Peristiwa KeyDown memerlukan metode yang menerima KeyEventArgs.
  2. Acara MouseClick mengharapkan metode yang mengambil MouseEventArgs.
  3. Metode Anda MultiHandler mengambil tipe EventArgs dasar.
  4. Karena KeyEventArgs dan MouseEventArgs keduanya mewarisi dari EventArgs, mereka dapat dengan aman diteruskan ke metode yang mengharapkan EventArgs.
  5. Pengkompilasi memungkinkan penugasan ini karena aman - MultiHandler dapat bekerja dengan instans apa pun EventArgs .

Ini adalah kontravariansi dalam tindakan: Anda dapat menggunakan metode dengan parameter "kurang spesifik" (jenis dasar) di mana parameter "lebih spesifik" (jenis turunan) diharapkan.

Kode

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

Poin-poin penting tentang kontravarian

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

Saat Menjalankan contoh ini, Anda akan melihat bahwa metode yang sama MultiHandler berhasil menangani peristiwa kunci dan mouse, menunjukkan bagaimana kontravariansi memungkinkan kode penanganan peristiwa yang lebih fleksibel dan dapat digunakan kembali.

Lihat juga