-
Notifications
You must be signed in to change notification settings - Fork 6k
Improve contravariance example in "Using Variance in Delegates" documentation #47052
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
fbfac29
Initial plan
Copilot 824bc81
Add complete contravariance example with better explanation
Copilot c3acde3
Address code review feedback: Use primary constructors, update code b…
Copilot 0a6b3c0
Update target framework to .NET 9.0 as requested
Copilot fb52fd7
Address review feedback: Add proper punctuation to list items and cla…
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
...s/covariance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
using System; | ||
|
||
namespace ContravarianceExample | ||
{ | ||
// <snippet1> | ||
// 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); | ||
} | ||
} | ||
// </snippet1> | ||
|
||
// <snippet2> | ||
// 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 | ||
// </snippet2> | ||
|
||
class Program | ||
{ | ||
static void Main() | ||
{ | ||
var form = new Form1(); | ||
form.DemonstrateEvents(); | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...variance-contravariance/snippets/using-variance-in-delegates/ContravarianceExample.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.