Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a .Net generic method behave differently for value types and reference types?

Tags:

c#

generics

I have two implementations of a method, one for value types and another for reference types:

public static Result<T> ImplRef(T arg) where T : class {...}
public static Result<T> ImplVal(T arg) where T : struct {...}

I want to write a method which calls the correct implementation like this

public static Result<T> Generic(T arg) {
    if (typeOf(T).IsValueType)
         return ImplVal(arg);
    else
         return ImplRef(arg);
}

Obviously, the above implementation doesn't compile. How can I do this with minimum of reflection?

like image 467
Alexey Romanov Avatar asked Jan 26 '26 02:01

Alexey Romanov


1 Answers

The idea with generics is usually to do the same logic with whichever inputs you are given, although obviously you need to be practical. Personally, I'd probably use two different methods, rather than brute-force them into the same method, but that would make it hard to call from a generic method just knowing about T. There is no way of satisfying the : class / : struct from static code, although the MakeGenericMethod approach might work, but will be an order of magnitude slower.

    // slow; use with caution
    public static Result<T> Generic<T>(T arg) {
        if (typeof(T).IsValueType)
            return (Result<T>)typeof(Program).GetMethod("ImplVal")
                .MakeGenericMethod(typeof(T))
                .Invoke(null, new object[] {arg});
        else
            return (Result<T>)typeof(Program).GetMethod("ImplRef")
                .MakeGenericMethod(typeof(T))
                .Invoke(null, new object[] { arg });
    }

(substitute typeof(Program) with the type that hosts the methods)

The alternative (as Jon notes) is to cache the (typed) delegate to the method:

public static Result<T> Generic<T>(T arg) {
    return Cache<T>.CachedDelegate(arg);
}

internal static class Cache<T>
{
    public static readonly Func<T, Result<T>> CachedDelegate;
    static Cache()
    {
        MethodInfo method;
        if (typeof(T).IsValueType)
            method = typeof(Program).GetMethod("ImplVal")
                .MakeGenericMethod(typeof(T));
        else
            method = typeof(Program).GetMethod("ImplRef")
                .MakeGenericMethod(typeof(T));
        CachedDelegate = (Func<T, Result<T>>)Delegate.CreateDelegate(
            typeof(Func<T, Result<T>>), method);
    }
}

More work, but will be plenty quick. The static constructor (or you could use a property/null check) ensures we only do the hard work once.

like image 127
Marc Gravell Avatar answered Jan 27 '26 18:01

Marc Gravell



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!