In this article, we are going to learn about how we can return null from a generic method in C#.
Let’s start.
Can We Return null From a Generic Method Directly?
Sometimes, when we create a generic method we might have to suppress exceptions and return null
, or returning null
might be intentional and part of the design. But whatever the case may be let’s see how we can do that.
We won’t add any logic to our generic method since our focus is on returning null
.
Let’s create a generic method and return null
directly:
public static T? FindItem<T>(List<T> items, string id) { return null; // Compiler error }
This code throws an exception:
Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
So, why is the compiler unhappy when we return null
directly?
The generic parameter T
can be a reference type, a nullable value type, or a non-nullable value type. Since we can’t assign null
to a non-nullable type the compiler prevents us from doing that.
Return null By Restricting the Generic Type to a Reference Type
Let’s restrict the generic method to accept only reference types as the generic type:
public static T FindItem<T>(List<T> items, string id) where T : class { return null; // No compiler error }
Now we are able to return null
because we can assign null to reference types and we have restricted the generic method to accept only reference type as a generic type.
Return null By Restricting the Generic Type to a Nullable Value Type
Let’s restrict the generic method to accept only value types by restricting T
to struct
:
public static T FindItem<T>(List<T> items, T id) where T : struct { return null; //Compiler error }
This code throws an exception as well:
Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
The compiler doesn’t allow us to return null
because we haven’t specified that the generic return type T
is nullable:
public static T? FindItem<T>(List<T> items, T id) where T : struct { return null; // No compiler error }
Returning null
here doesn’t return a null reference but the null value of the nullable value type.
null Reference vs null Value
Let’s understand a bit about how a null value differs from a null reference.
Let’s invoke the FindItem
with int
type:
int? item = FindItem(new List<int> { 1,2,3 }, 4); Console.WriteLine(item.Value);
We are returning null
from our generic method so when we access item.Value
it will throw InvalidOperationException
instead of NullReferenceException
like it does with null reference.
We can still do the null check be it a null reference or a nullable value type like this:
if (item is null) { Console.WriteLine("Item is null"); }
Return null Using the default Keyword
When we can’t restrict the generic type to a reference type or a value type we can’t return null
because the compiler doesn’t know whether we are returning a null reference or null value of the nullable value type.
So, how do we return null
in this case?
We can use the default
keyword:
public static T? FindItemOrDefault<T>(List<T> items, string id) { return default; }
default
returns the appropriate value based on the generic type. If we pass T
as nullable value type then it returns the null value of the nullable type, if we pass T
as reference type then it returns a null reference.
We have to be careful when we pass a non-nullable value type as a generic type because the return type changes based on the type.
If the type is int
then default
will return the default value of int
which is zero. If it is DateTime
then it returns 1/1/0001 12:00:00 AM.
Is Returning null a Bad Practice?
Returning null
can be considered bad practice because the caller has to explicitly handle the null checks and forgetting that can lead to unhandled exceptions.
However, if we have to return nulls as part of our design then at least we need to name the methods appropriately like FindItemOrDefault
or TryFindItem
etc so that the caller can understand that the method might also return null
.
If we have to return nulls and use them, we can check the values with the null-coalescing operator and avoid potential NullReferenceExceptions.
We can also enable the nullable feature (.NET 6) so that the compiler can warn us if there is a possible null
return.
Conclusion
In this article, we’ve learned how to return null in the generic method when the generic type is a reference type or a value type and how to use default when the generic type can be a value type or a reference type.