dotnet / dotnet/csharpstandard
Incorrect behaviour on conditional access to generic field where the generic type is a Nullable<T>
- Dominant language
- C#
- Stars
- 815
- Forks
- 99
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 16
Description
**Version Used**:
Master (27 April 2019)
**Steps to Reproduce**:
```csharp
using System;
public static class Program {
public static void Main() {
MutableStruct? field = new MutableStruct();
MutableStruct? param = new MutableStruct();
var printer = new Printer(field);
printer.Print(ref param);
printer.Print(ref param);
}
}
public class Printer
{
T field;
public Printer(T f) => field = f;
public void Print(ref T param)
{
Console.WriteLine(field?.ToString() + field?.ToString() + " " +
param?.ToString() + param?.ToString());
}
}
public struct MutableStruct
{
int i;
public override string ToString() => (++i).ToString();
}
```
[SharpLab](https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQxAgrgOwD4AEAMABLgIwDcAsAFC4DMxJAbMQEwMDshA3tYX8fVLNcAFkIBZCAEtMACgCU3XvxXj0MCMAA2cAMowMAYxgB+QgDMpcLQBNCAXkKY4AdwnrNO/UZgLKVFVUPbT0DdGMzAAcIBAgwBydXdw0Q73DfeX9A/gA3GMJIhBl4BATnNwAFIswSgB41FK8wiIA+WUtrG0zlbMLixAA6KuLZBDhzApi47oDsguqSoYXR8cnYsBmVAF9qHapqOlZCYZrEWoAVFuoeWb5ziytbLP5Dk5LZe/NFexaHzoTzM8+IdRMdlmMJvdout5D0lLdCABIUgATnajxsJgG5wA9t4ZABzBSEADUf1sWNx+MwRMUZIARIRGSS4SpEdC4pS8QZCcSyRywFzqbTNvw9nsDvRYD5kp5Qj5rnDioQpECBIQcTlEEUbHAGEQqTyacSfoRZCSSVJ5NjudVaf4tkA=)
**Expected Behavior**:
```
12 12
34 34
```
**Actual Behavior**:
```
11 11
11 11
```
**Discussion**:
You can see the correct behaviour if you change all occurrences of `MultableStruct?` to `MutableStruct`. If you remove the `ref`, then `param` shows the correct behaviour.
When there is conditional access of an unconstrained generic field, something similar to the following gets emitted (in C#):
```csharp
ref T reference = ref field;
object result;
if (default(T) == null)
{
T val = reference;
reference = ref val;
if (val == null)
{
result = null;
goto done;
}
}
result = reference.ToString(); // Constrained virtual call
done:
```
This has separate code paths for when `T` is a reference or value type, as value types must not be copied (but cannot be null), but reference types must be checked for null (but the reference can be copied).
Unfortunately `default(Nullable)` boxes to `null`, so when `T` is a `Nullable`, we hit the code path meant for reference types, and copy it before calling `ToString()`.
Contributor guide
Assessment
This issue has not been assessed yet.