Disambiguate method trick based on generic constraint

Imagine the following scenario: I want to define a method AsNullable that returns the parameter type if it is a class or a nullable of the parameter type if it is a struct:

    public static T? AsNullable<T>(this T item) where T : struct
    {
        return item;
    }
 
    public static T AsNullable<T>(this T item) where T : class
    {
        return item;
    }
 

The problem here is that C# does not include generic constraints in method signature. So C# compiler complains that this is ambiguous.

So we can move the two extension methods in different classes but in this case, we are just moving the problem. Indeed, when we will try to use myExpression.AsNullable() compilers will still complain.

Today, I found a trick to avoid this: add a null parameter to disambiguate this scenario:

    public static T? AsNullable<T>(this T item, T? @null) where T : struct
    {
        return item;
    }
 
    public static T AsNullable<T>(this T item, T @null) where T : class
    {
        return item;
    }

Then, you can just call myExpression.AsNullable(null) and it works fine.

But, sadly, it does not work if @null is optional and you don’t want to specify it in the caller.

However, you can fix it using the following code (thanks Akuma for the tip):

    public struct ClassRequirement<T> where T : class
    {
    }
 
    public struct StructRequirement<T> where T : struct
    {
    }
 
    public static T? AsNullable<T>(this T item, StructRequirement<T> foo = default(StructRequirement<T>)) where T : struct
    {
        return item;
    }
 
    public static T AsNullable<T>(this T item, ClassRequirement<T> foo = default(ClassRequirement<T>)) where T : class
    {
        return item;
    }

And you can just call myExpression.AsNullable() which is nicer.

 

Now what about other constraints such as interface implementation?

    public static void Foo<T>(T t) where T : I1
    {
        Bar(t);
    }
 
    public static void Bar<T>(T t) where T : I1 { }
    public static void Bar<T>(T t) where T : I2 { }

In this case, you can also add a new parameter to disambiguate it:

    public struct I1Requirement<T> where T : I1
    {
    }
 
    public struct I2Requirement<T> where T : I2
    {
    }
 
    public static void Bar<T>(this T item, I1Requirement<T> foo = default(I1Requirement<T>)) where T : I1
    {
    }
 
    public static void Bar<T>(this T item, I2Requirement<T> foo = default(I2Requirement<T>)) where T : I2
    {
    }

 

Hope that helps.

Leave a Reply

Your email address will not be published. Required fields are marked *