简体   繁体   中英

How to avoid boxing of value types

Suppose I have some class that invokes a Func based upon the generic type passed to it, and I have a desired public interface for that class something like this:

var r = new Resolver(); 
var i = r.Invoke(10); // invokes some function `Func<int,int>`
var j = r.Invoke("Hello"); // Same, but `Func<string,string>`
var k = r.Invoke(10, 10); // Same but 'Func<int,int,int>`

I have an implementation like this:

class Resolver {
    readonly IDictionary<Type, Func<object, object>> _funcs = new Dictionary<Type, Func<object, object>>();

    public Resolver() {
        _funcs.Add(typeof(int), o => (int)o*(int)o);
        _funcs.Add(typeof(string), o => (string)o + (string)o);
        // and so on; 
    }

    public T Invoke<T>(T t1) {
        return (T) _funcs[typeof (T)](t1);
    }

    public T Invoke<T>(T t1, T t2) {
        return (T)_funcs[typeof(T)](t1);
    }
}

but performance is horrible for value types because of the boxing caused by the internal implementation of the Func<,> having object as generic types.

Is there a way to implement my desired public interface avoiding boxing for value types? I also wouldn't mind static type safety inside the implementation, but could live without.

You could do the following simple trick (not any more or less type safe than your current implementation):

class Resolver
{
    readonly IDictionary<Type, object> _unaryFuncs = new Dictionary<Type, object>();
    readonly IDictionary<Type, object> _binaryFuncs = new Dictionary<Type, object>();

    public Resolver()
    {
        _unaryFuncs.Add(typeof(int),  new Func<int, int>(o => o * o));
        _unaryFuncs.Add(typeof(string), new Func<string, string(o => o + o));
        _binaryFuncs.Add(typeof(int), new Func<int, int, int>((x, y) => x + y));
        // and so on; 
    }

    public T Invoke<T>(T t1)
    {
        var f = _unaryFuncs[typeof(T)] as Func<T, T>;
        return f(t1);
    }

    public T Invoke<T>(T t1, T t2)
    {
        var f = _binaryFuncs[typeof(T)] as Func<T, T, T>;
        return f(t1, t2);
    }
}

You may want to add some error checking like checking that

  1. a T function is registered, before getting it from the dictionary.
  2. it is not null after the as cast.

And add typesafe registration functions:

public void Register<T>(Func<T, T> unaryFunc)
{
    _unaryFuncs[typeof(T)] = unaryFunc;
}

public void Register<T>(Func<T, T, T> binaryFunc)
{
    _binaryFuncs[typeof(T)] = binaryFunc;
}

You could use a static generic variable to cache your resolver. In my quick testing, this took about a third of the time to execute and avoids boxing.

Since ResolverCache<Func<int,int>>.Resolver is a different variable than ResolverCache<Func<string,string>>.Resolver , you can store the different resolvers in a type-safe way.

class Resolver
{
    static class ResolverCache<T>
    {
        public static T Resolver { get; set; }
    }

    void AddResolver<T>(T resolver)
    {
        ResolverCache<T>.Resolver = resolver;
    }

    public Resolver()
    {
        Func<int, int> intResolver = o => (int)o * (int)o;
        Func<int, int, int> intResolver2 = (o, p) => (int)o * (int)p;
        Func<string, string> stringResolver = o => (string)o + (string)o;

        AddResolver(intResolver);
        AddResolver(intResolver2);
        AddResolver(stringResolver);

        // and so on; 
    }

    public T Invoke<T>(T t1)
    {
        var resolver = ResolverCache<Func<T, T>>.Resolver ?? (v => { throw new Exception("No resolver registered."); });
        return resolver(t1);
    }

    public T Invoke<T>(T t1, T t2)
    {
        var resolver = ResolverCache<Func<T, T, T>>.Resolver ?? ((v, u) => { throw new Exception("No resolver registered."); });
        return resolver(t1, t2);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM