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.