To me, it looks perfectly fine, to pass null as the parameter to Task.FromResult
.
No, that is a bad idea.
If the caller specifies a non-nullable type for T
then default(T)
can be considered "undefined" (it's actually null
, but that's a major shortcoming of C# 8.0's implementation of non-nullable-reference-types (i.e. they can still be null
, grrrr). Consider:
// Compiled with C# 8.0's non-nullable reference-types enabled.
Task<String> task = GetDefaultTask<String>();
String result = await task;
Console.WriteLine( result.Length ); // <-- NullReferenceException at runtime even though the C# compiler reported `result` cannot be null.
Avoid using default
/default(T)
in C# 8.0 for generic types without adequate type-constraints.
There are a few solutions to this problem:
1: Specify a caller-provided default-value:
public Task<T> GetDefaultTask<T>( T defaultValue )
{
return Task.FromResult( defaultValue );
}
So the call-site needs to be updated and the C# compiler will give a warning or error if the caller tries to use null
instead of an exception at runtime:
Task<String> task = GetDefaultTask<String>( defaultValue: null ); // <-- compiler error or warning because `null` cannot be used here.
String result = await task;
Console.WriteLine( result.Length );
2: Add struct vs. class constraints on different methods:
The default(T)
of a struct
/value-type can be meaningful (or it could be just as dangerous as a null
...), as we can safely use default(T)
where T : struct
but not default(T)
where T : class
, we can add different overloads for that case:
public Task<T> GetDefaultTask<T>()
where T : struct
{
return Task.FromResult( default(T) );
}
public Task<T> GetDefaultTask<T>( T defaultValue )
where T : class
{
return Task.FromResult( defaultValue );
}
(Note that you can't overload methods based purely on generic type constraints - you can only overload by generic parameter-count and ordinarily parameter types.