tags:

views:

1003

answers:

4

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?

A: 

Is there a reason you can't go with the two separate implementations (that possibly both use a third common implementation)?

Using typeof means you're doing RTTI which will make your app slower. Using the compiler (by using separate implementations for class and struct) will be much quicker, since all the decisions about types will be handled by the compiler during JIT compiling.

Stewart Johnson
I intend to expose the separate implementations, but I still want to have an implementation which will be usable without constraints as well.
Alexey Romanov
+1  A: 

Do you actually use the constraints in ImplRef and ImplVal? If not (i.e. if it's just so you can behave differently) you could drop the constraints, make the methods private, and just call them appropriately from Generic - or possibly have:

public static Result<T> ImplRef(T arg) where T : class 
{
    return ImplRefImpl(arg);
}

private static Result<T> ImplRefImpl(T arg)
{
    // Real code
}

public static Result<T> ImplVal(T arg) where T : struct
{
    return ImplValImpl(arg);
}

private static Result<T> ImplValImpl(T arg)
{
    // Real code
}

You should consider what to do if T is a nullable type, by the way - it doesn't actually satisfy either the class or struct constraints.

By the way, another way of testing for T being a value type (which may be more efficient - not sure) is:

if (default(T) == null)

Note that this will "pass" for a nullable type, unlike your test of IsValueType, so the behaviour isn't identical.

Jon Skeet
If I wasn't using the constraints, then I wouldn't need to ask the question. I didn't know that Nullable<T> doesn't satisfy struct constraint; thanks!
Alexey Romanov
Well, you might have wanted the constraints for "external" callers, to make sure they did the right thing, rather than because of the extra powers the constraints give you. Perhaps you could work around it so you didn't need the constraints instead? How are you using them?
Jon Skeet
In fact I _don't_ want any external callers to care which method to use. That's precisely why I want to provide Generic, even if calling it has some performance penalty compared to Ref... methods.
Alexey Romanov
I am writing a generic class for holding mutable references to immutable values thread-safely. For reference types I can use Interlocked.Exchange(ref T, T, T); for value types in general I need to use locks (for some primitive value types, I can do better).
Alexey Romanov
+4  A: 

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.

Marc Gravell
Of course you could call MakeGenericMethod once, and build a delegate from it with Delegate.CreateDelegate. Seems a lot of work though...
Jon Skeet
@Jon: and you'd need a static class somewhere to cache the delegate... I'll add to the example, though...
Marc Gravell
Yes, it's workable. I was just hoping there was a faster way. We don't need a static class, just a static field in the same class. Is Dictionary<K,V> threadsafe if you just add and read values and don't enumerate over it or delete anything? I think so.
Alexey Romanov
Posted my comment before seeing your updated version. Yes, that's better than what I was thinking about.
Alexey Romanov
Can use Dynamic Reflection Library (http://www.codeplex.com/Dynamic/) to make it even faster.
Alexey Romanov
I wouldn't assume that the DRL will make it faster. After the first time, calling the delegate will be blindingly fast.
Jon Skeet
@alexey_r - no, that won't be threadsafe - you'd need to synchronize; and to second Jon, I very much doubt that DRL would make any difference here
Marc Gravell
No, probably not. Thank you again!
Alexey Romanov
A: 

Why not let the compiler choose?

public static Result<T> ToResult<T>(this T arg) where T: class
{ return new ImplRef(arg); }

public static Result<T> ToResult<T>(this T arg) where T: struct
{ return new ImplVal(arg)

to be used like:

"hi".ToResult();
3.ToResult();