T? x = default
does not work with value types?
#7462
-
I ran into an issue the other day involving default parameters on a generic function. I was using code similar to the following: private static List<T> MakeList<T>(T a, T b, T c, T? d = default)
{
List<T> ret = new();
ret.Add(a);
ret.Add(b);
ret.Add(c);
if (d is not null)
ret.Add(d);
return d;
} When called, even if the fourth parameter is not passed, the returned list still has four objects, and the fourth is not null, but default(T) (not // this obviously works find:
MakeList(1, 2, 3, 4);
// { 1, 2, 3, 4 }
// but this doesn't:
MakeList(1, 2, 3);
// { 1, 2, 3, 0 } Confused, I debugged and saw that // source
public class TestClass
{
public void TestMethod<T>(T? param = default)
{ }
public void TestTestMethod() =>
TestMethod<int>();
}
// decompilation
public class TestClass
{
[System.Runtime.CompilerServices.NullableContext(2)]
public void TestMethod<T>(T param = default(T))
{
}
public void TestTestMethod()
{
TestMethod(0);
}
} The It's only when I add a // source
public class TestClass
{
public void TestMethod<T>(T? param = default)
where T : struct
{ }
public void TestTestMethod() =>
TestMethod<int>();
}
// decompilation
public class TestClass
{
public void TestMethod<T>(Nullable<T> param = null) where T : struct
{
}
public void TestTestMethod()
{
TestMethod<int>(null);
}
} This can, thankfully, be worked around thanks to how nullable value types work, which gives the overloads different parameter types, but it's clunky due to code repetition: // `T?` is actually `Nullable<T>`, which is distinct from `T` when `T:class`
private static List<T> MakeList<T>(T a, T b, T c, T? d = default)
where T : struct
{
List<T> ret = new();
ret.Add(a);
ret.Add(b);
ret.Add(c);
if (d is not null)
ret.Add(d.Value); // `Value` deref now needed
return d;
}
private static List<T> MakeList<T>(T a, T b, T c, T? d = default)
where T : class
{
List<T> ret = new();
ret.Add(a);
ret.Add(b);
ret.Add(c);
if (d is not null)
ret.Add(d);
return d;
} This would just get worse the more complex the function. I can't even pull the logic into a "core" function because the compiler wants to call // error CS0029: Cannot implicitly convert type 'System.Collections.Generic.List<T?>' to 'System.Collections.Generic.List<T>'
public static List<T> MakeList<T>(T a, T b, T c, T? d = null)
where T : struct =>
MakeListCore(a, b, c, d);
// error CS1503: Argument 4: cannot convert from 'T?' to 'T'
public static List<T> MakeList<T>(T a, T b, T c, T? d = null)
where T : struct =>
MakeListCore<T>(a, b, c, d);
public static List<T> MakeList<T>(T a, T b, T c, T? d = null)
where T : class =>
MakeListCore(a, b, c, d);
private static List<T> MakeListCore<T>(T a, T b, T c, T? d)
{
List<T> ret = new();
ret.Add(a);
ret.Add(b);
ret.Add(c);
if (d is not null)
ret.Add(d);
return ret;
} I could theoretically use LINQ with public static List<T> MakeList<T>(T a, T b, T c, T? d = null)
where T : struct =>
MakeListCore(a, b, c, d).Cast<T>().ToList(); Is this behavior documented anywhere? Should there be an analyzer to warn about this? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
Yes. This is the expected and documented behavior in the spec. For an unconstrained type parameter,
This is very expected behavior that many libraries and apps depend on. You could write an analyzer for your own projects, but it would not be something we could make a standard one. |
Beta Was this translation helpful? Give feedback.
-
Unfortunately, the runtime provides no way to operate over all types (reference and value), while also providing uniform way to operate over null values for both. It's been a fundamental limitation since 2003 when generics were added. |
Beta Was this translation helpful? Give feedback.
Yes. This is the expected and documented behavior in the spec. For an unconstrained type parameter,
T?
means "can be default".This is very expected behavior that many libraries and apps depend on.
You could write an analyzer for your own projects, but it would not be something we could make a standard one.