如何避免值类型的装箱问题

3
假设我有一个类,根据传递给它的泛型类型调用一个基于Func的方法,并且我希望该类有一个期望的公共接口,类似于以下内容:
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>`

我有一个像这样的实现:

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);
    }
}

但是,由于内部实现的Func<,>具有object作为通用类型,因此对于值类型来说,性能非常差。

有没有一种方法可以实现我想要的公共接口,避免值类型的装箱?我也不介意在实现中使用静态类型安全,但是如果没有也可以。

2个回答

5
您可以使用以下简单的技巧(与您当前的实现方式一样安全):
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);
    }
}

您可能希望添加一些错误检查,比如在从字典中获取函数之前检查是否已注册 T 函数。

  1. 在从字典中获取函数之前,请检查是否已注册 T 函数。
  2. as 转换后它不是 null

并添加类型安全的注册函数:

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;
}

这是一个避免我所假设的问题的好方法。然而,基于我自己的测试结果显示,装箱并不是我正在经历的性能问题的主要原因。尽管如此,我还是感谢您的回复。谢谢! - jdphenix

2
您可以使用静态泛型变量来缓存您的解析器。在我的快速测试中,这个方法执行的时间只有三分之一,并且避免了装箱。
由于“ResolverCache>.Resolver”与“ResolverCache>.Resolver”是不同的变量,因此您可以以类型安全的方式存储不同的解析器。
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);
    }
}

+1 完美。我正在寻找一种在不产生装箱的情况下传递泛型的方法,这正好给了我解决问题所需的推动力。谢谢! - Josh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接