當您將方法指派給委派時, 共變數 和 反變數 可提供彈性來比對具有方法簽章的委派類型。 共變數允許方法的傳回型別比委派中所定義的衍生類型還要多。 Contravariance 允許方法具有比委派類型中更低衍生的參數類型。
範例 1:共變數
說明
這個範例示範如何搭配方法使用委派,這些方法具有衍生自委派簽章中傳回型別的方法。
DogsHandler 返回的資料類型是 Dogs 類型,該類型從委派中定義的 Mammals 類型衍生而來。
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;
}
}
範例 2:反變數
說明
此範例示範如何使用委派來搭配那些其參數類型為委派簽名參數基礎類型的方法。 使用反變數時,您可以使用一個事件處理程式,而不是個別的處理程式。 下列範例會使用兩個委派:
定義金鑰事件簽章的自定義
KeyEventHandler委派。 其簽章為:public delegate void KeyEventHandler(object sender, KeyEventArgs e)定義滑鼠事件簽章的自定義
MouseEventHandler委派。 其簽章為:public delegate void MouseEventHandler(object sender, MouseEventArgs e)
此範例會使用 EventArgs 參數定義事件處理程式,並用它來處理按鍵和滑鼠事件。 這可運作,因為 EventArgs 是範例中所定義之自定義 KeyEventArgs 和 MouseEventArgs 類別的基底類型。 Contravariance 允許接受基底類型參數的方法,用於提供衍生類型參數的事件。
反變數在此範例中的運作方式
當您訂閱事件時,編譯程式會檢查事件處理程式方法是否與事件的委派簽章相容。 使用反變數:
-
KeyDown事件期望一個接受KeyEventArgs的方法。 -
MouseClick事件需要一個帶有MouseEventArgs的方法。 - 您的
MultiHandler方法會採用基底類型EventArgs。 - 由於
KeyEventArgs和MouseEventArgs兩者都繼承自EventArgs,因此可以安全地傳遞至預期EventArgs的方法。 - 編譯程式允許此指派,因為它很安全 -
MultiHandler可以使用任何EventArgs實例。
這是在實踐中的反變:您可以在預期需要「更特定」(衍生類型)參數的情況下,使用包含「較不特定」(基底類型)參數的方法。
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);
}
}
反變數的要點
// 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
當您執行此範例時,您會看到相同的 MultiHandler 方法成功處理按鍵和滑鼠事件,示範反變數如何啟用更有彈性且可重複使用的事件處理程序代碼。