デリゲートにメソッドを割り当てると、 共変性 と 反変性 によって、デリゲート型とメソッド シグネチャを柔軟に照合できます。 共変性により、メソッドの戻り値の型の派生を、デリゲートに定義されている型よりも強くできます。 反変性により、デリゲート型のメソッドよりも派生度の低いパラメーター型を持つメソッドが許可されます。
例 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: 反変性
説明
この例は、型がデリゲート シグネチャ パラメーター型の基本データ型であるパラメーターを持つメソッドでデリゲートを使用する方法を示しています。 反変性では、個別のハンドラーの代わりに 1 つのイベント ハンドラーを使用できます。 次の例では、2 つのデリゲートを使用します。
キー イベントの署名を定義するカスタム
KeyEventHandler
デリゲート。 そのシグネチャは次のとおりです。public delegate void KeyEventHandler(object sender, KeyEventArgs e)
マウス イベントのシグネチャを定義するカスタム
MouseEventHandler
デリゲート。 そのシグネチャは次のとおりです。public delegate void MouseEventHandler(object sender, MouseEventArgs e)
この例では、 EventArgs パラメーターを持つイベント ハンドラーを定義し、それを使用してキーイベントとマウス イベントの両方を処理します。 これは、 EventArgs が、この例で定義されているカスタム KeyEventArgs
クラスと MouseEventArgs
クラスの両方の基本型であるために機能します。 反変性により、基本型パラメーターを受け取るメソッドを、派生型パラメーターを提供するイベントに使用できます。
この例での反変性のしくみ
イベントをサブスクライブすると、コンパイラはイベント ハンドラー メソッドがイベントのデリゲート シグネチャと互換性があるかどうかを確認します。 反変性の場合:
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
メソッドがキー イベントとマウス イベントの両方を正常に処理し、反変性によって、より柔軟で再利用可能なイベント処理コードが可能になるしくみがわかります。
こちらも参照ください
.NET