From fbfac2907a63dc1a4f6546cca1031b38af16fd4b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 18:01:31 +0000 Subject: [PATCH 1/5] Initial plan From 824bc81c39ce78d025c72ba139e95b0d311cb87f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 18:08:09 +0000 Subject: [PATCH 2/5] Add complete contravariance example with better explanation Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- .../ContravarianceExample.cs | 138 ++++++++++++++++++ .../ContravarianceExample.csproj | 9 ++ .../using-variance-in-delegates.md | 48 +++--- 3 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs create mode 100644 docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj diff --git a/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs new file mode 100644 index 0000000000000..88a5b7864eb37 --- /dev/null +++ b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs @@ -0,0 +1,138 @@ +using System; + +namespace ContravarianceExample +{ + // + // Custom EventArgs classes to demonstrate the hierarchy + public class KeyEventArgs : EventArgs + { + public string KeyCode { get; set; } + public KeyEventArgs(string keyCode) => KeyCode = keyCode; + } + + public class MouseEventArgs : EventArgs + { + public int X { get; set; } + public int Y { get; set; } + public MouseEventArgs(int x, int y) { X = x; Y = 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 + // + + class Program + { + static void Main() + { + var form = new Form1(); + form.DemonstrateEvents(); + + Console.WriteLine("\nPress any key to exit..."); + Console.ReadKey(); + } + } +} \ No newline at end of file diff --git a/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj new file mode 100644 index 0000000000000..eae3a42707496 --- /dev/null +++ b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj @@ -0,0 +1,9 @@ + + + + Exe + net8.0 + enable + + + \ No newline at end of file diff --git a/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md b/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md index b46825554d8bb..1947bed614ad8 100644 --- a/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md +++ b/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md @@ -51,43 +51,41 @@ class Program This example demonstrates how delegates can be used with methods that have parameters whose types are base types of the delegate signature parameter type. With contravariance, you can use one event handler instead of separate handlers. The following example makes use of two delegates: -- A delegate that defines the signature of the [Button.KeyDown](xref:System.Windows.Forms.Control.KeyDown) event. Its signature is: +- A `KeyEventHandler` delegate that defines the signature of a key event. Its signature is: ```csharp public delegate void KeyEventHandler(object sender, KeyEventArgs e) ``` -- A delegate that defines the signature of the [Button.MouseClick](xref:System.Windows.Forms.Control.MouseDown) event. Its signature is: +- A `MouseEventHandler` delegate that defines the signature of a mouse event. Its signature is: ```csharp public delegate void MouseEventHandler(object sender, MouseEventArgs e) ``` -The example defines an event handler with an parameter and uses it to handle both the `Button.KeyDown` and `Button.MouseClick` events. It can do this because is a base type of both and . +The example defines an event handler with an parameter and uses it to handle both key and mouse events. This works because is a base type of both `KeyEventArgs` and `MouseEventArgs`. Contravariance allows a method that accepts a base type parameter to be used for events that provide derived type parameters. + +### How contravariance works in this example + +When you subscribe to an event, the compiler checks if your event handler method is compatible with the event's delegate signature. With contravariance: + +1. The `KeyDown` event expects a method that takes `KeyEventArgs` +1. The `MouseClick` event expects a method that takes `MouseEventArgs` +1. Your `MultiHandler` method takes the base type `EventArgs` +1. Since `KeyEventArgs` and `MouseEventArgs` both inherit from `EventArgs`, they can be safely passed to a method expecting `EventArgs` +1. The compiler allows this assignment because it's safe - the `MultiHandler` can work with any `EventArgs` instance + +This is contravariance in action: you can use a method with a "less specific" (base type) parameter where a "more specific" (derived type) parameter is expected. ### Code - -```csharp -// Event handler that accepts a parameter of the EventArgs type. -private void MultiHandler(object sender, System.EventArgs e) -{ - label1.Text = System.DateTime.Now.ToString(); -} - -public Form1() -{ - InitializeComponent(); - - // You can use a method that has an EventArgs parameter, - // although the event expects the KeyEventArgs parameter. - this.button1.KeyDown += this.MultiHandler; - - // You can use the same method - // for an event that expects the MouseEventArgs parameter. - this.button1.MouseClick += this.MultiHandler; - -} -``` + +[!code-csharp[Contravariance example](snippets/using-variance-in-delegates/ContravarianceExample.cs#snippet1)] + +### Key points about contravariance + +[!code-csharp[Contravariance explanation](snippets/using-variance-in-delegates/ContravarianceExample.cs#snippet2)] + +When you run this example, you'll see that the same `MultiHandler` method successfully handles both key and mouse events, demonstrating how contravariance enables more flexible and reusable event handling code. ## See also From c3acde3f1671ad8cfbbc8a08ee4b8884530b334b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:29:33 +0000 Subject: [PATCH 3/5] Address code review feedback: Use primary constructors, update code block syntax, remove unnecessary console output Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- .../ContravarianceExample.cs | 15 +++++---------- .../using-variance-in-delegates.md | 4 ++-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs index 88a5b7864eb37..0ab038d2b1b90 100644 --- a/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs +++ b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs @@ -4,17 +4,15 @@ namespace ContravarianceExample { // // Custom EventArgs classes to demonstrate the hierarchy - public class KeyEventArgs : EventArgs + public class KeyEventArgs(string keyCode) : EventArgs { - public string KeyCode { get; set; } - public KeyEventArgs(string keyCode) => KeyCode = keyCode; + public string KeyCode { get; set; } = keyCode; } - public class MouseEventArgs : EventArgs + public class MouseEventArgs(int x, int y) : EventArgs { - public int X { get; set; } - public int Y { get; set; } - public MouseEventArgs(int x, int y) { X = x; Y = y; } + public int X { get; set; } = x; + public int Y { get; set; } = y; } // Define delegate types that match the Windows Forms pattern @@ -130,9 +128,6 @@ static void Main() { var form = new Form1(); form.DemonstrateEvents(); - - Console.WriteLine("\nPress any key to exit..."); - Console.ReadKey(); } } } \ No newline at end of file diff --git a/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md b/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md index 1947bed614ad8..25ef2df441d64 100644 --- a/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md +++ b/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md @@ -79,11 +79,11 @@ This is contravariance in action: you can use a method with a "less specific" (b ### Code -[!code-csharp[Contravariance example](snippets/using-variance-in-delegates/ContravarianceExample.cs#snippet1)] +:::code language="csharp" source="snippets/using-variance-in-delegates/ContravarianceExample.cs" id="snippet1"::: ### Key points about contravariance -[!code-csharp[Contravariance explanation](snippets/using-variance-in-delegates/ContravarianceExample.cs#snippet2)] +:::code language="csharp" source="snippets/using-variance-in-delegates/ContravarianceExample.cs" id="snippet2"::: When you run this example, you'll see that the same `MultiHandler` method successfully handles both key and mouse events, demonstrating how contravariance enables more flexible and reusable event handling code. From 0a6b3c0e14532f1e5ef23d0210769a86f897c277 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:38:52 +0000 Subject: [PATCH 4/5] Update target framework to .NET 9.0 as requested Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- .../using-variance-in-delegates/ContravarianceExample.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj index eae3a42707496..2dfe8aaebbfb1 100644 --- a/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj +++ b/docs/csharp/programming-guide/concepts/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable From fb52fd794e52c94521ae3015c88a50748e2faa9e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:23:04 +0000 Subject: [PATCH 5/5] Address review feedback: Add proper punctuation to list items and clarify custom types Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- .../using-variance-in-delegates.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md b/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md index 25ef2df441d64..86e414362d491 100644 --- a/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md +++ b/docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates.md @@ -51,29 +51,29 @@ class Program This example demonstrates how delegates can be used with methods that have parameters whose types are base types of the delegate signature parameter type. With contravariance, you can use one event handler instead of separate handlers. The following example makes use of two delegates: -- A `KeyEventHandler` delegate that defines the signature of a key event. Its signature is: +- A custom `KeyEventHandler` delegate that defines the signature of a key event. Its signature is: ```csharp public delegate void KeyEventHandler(object sender, KeyEventArgs e) ``` -- A `MouseEventHandler` delegate that defines the signature of a mouse event. Its signature is: +- A custom `MouseEventHandler` delegate that defines the signature of a mouse event. Its signature is: ```csharp public delegate void MouseEventHandler(object sender, MouseEventArgs e) ``` -The example defines an event handler with an parameter and uses it to handle both key and mouse events. This works because is a base type of both `KeyEventArgs` and `MouseEventArgs`. Contravariance allows a method that accepts a base type parameter to be used for events that provide derived type parameters. +The example defines an event handler with an parameter and uses it to handle both key and mouse events. This works because is a base type of both the custom `KeyEventArgs` and `MouseEventArgs` classes defined in the example. Contravariance allows a method that accepts a base type parameter to be used for events that provide derived type parameters. ### How contravariance works in this example When you subscribe to an event, the compiler checks if your event handler method is compatible with the event's delegate signature. With contravariance: -1. The `KeyDown` event expects a method that takes `KeyEventArgs` -1. The `MouseClick` event expects a method that takes `MouseEventArgs` -1. Your `MultiHandler` method takes the base type `EventArgs` -1. Since `KeyEventArgs` and `MouseEventArgs` both inherit from `EventArgs`, they can be safely passed to a method expecting `EventArgs` -1. The compiler allows this assignment because it's safe - the `MultiHandler` can work with any `EventArgs` instance +1. The `KeyDown` event expects a method that takes `KeyEventArgs`. +1. The `MouseClick` event expects a method that takes `MouseEventArgs`. +1. Your `MultiHandler` method takes the base type `EventArgs`. +1. Since `KeyEventArgs` and `MouseEventArgs` both inherit from `EventArgs`, they can be safely passed to a method expecting `EventArgs`. +1. The compiler allows this assignment because it's safe - the `MultiHandler` can work with any `EventArgs` instance. This is contravariance in action: you can use a method with a "less specific" (base type) parameter where a "more specific" (derived type) parameter is expected.