Skip to content

Commit dc05bd5

Browse files
CopilotBillWagnergewarren
authored
Add explanation for contravariance behavior with anonymous functions (#47707)
* Initial plan * Add explanation for contravariance behavior with anonymous functions Co-authored-by: BillWagner <[email protected]> * Remove "explicit" when describing var keyword usage The var keyword represents implicit typing, not explicit typing. This correction addresses the terminology issue pointed out in the code review. Co-authored-by: BillWagner <[email protected]> * Add periods to code comments for proper punctuation Co-authored-by: BillWagner <[email protected]> * Apply suggestions from code review Co-authored-by: Genevieve Warren <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: BillWagner <[email protected]> Co-authored-by: Bill Wagner <[email protected]> Co-authored-by: Genevieve Warren <[email protected]>
1 parent ccfcd50 commit dc05bd5

File tree

1 file changed

+51
-1
lines changed

1 file changed

+51
-1
lines changed

docs/csharp/programming-guide/concepts/covariance-contravariance/using-variance-for-func-and-action-generic-delegates.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,57 @@ class Program
7878
}
7979
}
8080
```
81-
81+
82+
## Contravariance and anonymous functions
83+
84+
When working with anonymous functions (lambda expressions), you might encounter counterintuitive behavior related to contravariance. Consider the following example:
85+
86+
```csharp
87+
public class Person
88+
{
89+
public virtual void ReadContact() { /*...*/ }
90+
}
91+
92+
public class Employee : Person
93+
{
94+
public override void ReadContact() { /*...*/ }
95+
}
96+
97+
class Program
98+
{
99+
private static void Main()
100+
{
101+
var personReadContact = (Person p) => p.ReadContact();
102+
103+
// This works - contravariance allows assignment.
104+
Action<Employee> employeeReadContact = personReadContact;
105+
106+
// This causes a compile error: CS1661.
107+
// Action<Employee> employeeReadContact2 = (Person p) => p.ReadContact();
108+
}
109+
}
110+
```
111+
112+
This behavior seems contradictory: if contravariance allows assigning a delegate that accepts a base type (`Person`) to a delegate variable expecting a derived type (`Employee`), why does direct assignment of the lambda expression fail?
113+
114+
The key difference is **type inference**. In the first case, the lambda expression is first assigned to a variable with type `var`, which causes the compiler to infer the lambda's type as `Action<Person>`. The subsequent assignment to `Action<Employee>` succeeds because of contravariance rules for delegates.
115+
116+
In the second case, the compiler cannot directly infer that the lambda expression `(Person p) => p.ReadContact()` should have type `Action<Person>` when it's being assigned to `Action<Employee>`. The type inference rules for anonymous functions don't automatically apply contravariance during the initial type determination.
117+
118+
### Workarounds
119+
120+
To make direct assignment work, you can use explicit casting:
121+
122+
```csharp
123+
// Explicit cast to the desired delegate type.
124+
Action<Employee> employeeReadContact = (Action<Person>)((Person p) => p.ReadContact());
125+
126+
// Or specify the lambda parameter type that matches the target delegate.
127+
Action<Employee> employeeReadContact2 = (Employee e) => e.ReadContact();
128+
```
129+
130+
This behavior illustrates the difference between delegate contravariance (which works after types are established) and lambda expression type inference (which occurs during compilation).
131+
82132
## See also
83133

84134
- [Covariance and Contravariance (C#)](./index.md)

0 commit comments

Comments
 (0)