使用委托中的变体 - C#

使用委托中的变体 - C#

将方法分配给委托时, 协变 和 逆变 为使用方法签名匹配委托类型提供了灵活性。 协变允许方法具有的派生返回类型多于委托中定义的类型。 逆变允许方法具有的派生参数类型少于委托类型中的类型。

示例 1:协变

DESCRIPTION

本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。 返回的 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:逆变

DESCRIPTION

本示例演示如何将委托与具有参数的方法一起使用,这些参数的类型是委托签名参数类型的基类型。 通过逆变可以使用一个事件处理程序而不是多个单独的处理程序。 下面的示例使用两个委托:

定义键事件签名的自定义 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 方法成功处理键和鼠标事件,演示逆变如何实现更灵活和可重用的事件处理代码。

另请参阅

委托中的变体 (C#)

对 Func 和 Action 泛型委托使用变体 (C#)

相关创意

外科风云 Episode 1– Download APP to Enjoy Now!
365BET-官网

外科风云 Episode 1– Download APP to Enjoy Now!

📅 12-21 👁️ 3594
淘宝怎么看热力图
365bet娱乐场官网备用

淘宝怎么看热力图

📅 09-01 👁️ 7842
qq现金贷找不到?在哪找
365bet娱乐场官网备用

qq现金贷找不到?在哪找

📅 08-03 👁️ 3505