无需分配内存的委托或其他通过地址调用方法的方式?

15

我需要能够使用Mono在C#中基于函数指针调用单个方法。Delegate(委托)可用于此目的,但它们似乎每次设置委托时都会分配52字节的内存(不是 +=,只是使用 = 设置委托,因此委托引用的始终是一个方法)。

这个委托每秒钟会多次更改,这导致GC定期启动,我想避免这种情况。

我不介意最初的内存分配,但是否有一种方法可以防止每次更改单个委托值时都进行一次内存分配?

如果没有,除了Delegate之外,还有其他动态调用C#方法的方法吗,这些方法不会每次更改地址时都分配任何内存?


2
委托对象如此频繁地更改似乎表明您的设计可能有问题。答案不是“如何停止我的委托对象使用太多内存”,而是“如何减少委托对象的更改次数”? - Liam
你能详细说明一下为什么你总是在更改委托吗?也许有其他的选择。 - Baldrick
1
我觉得这个实际问题很有趣。不知道是否有一种方法可以防止委托的分配。 - Serve Laurijssen
利用接口,让多态发挥作用怎么样? - Simon Whitehead
3
@ServéLaurijssen http://blogs.msdn.com/b/shawnhar/archive/2007/07/09/delegates-events-and-garbage.aspx?Redirected=true “实际上,构造一个新的委托对象是不可能不分配内存的。” - Baldrick
显示剩余5条评论
1个回答

16
任何像这样编写的代码
Action action = foo.DoSomething;

最终编译成这样:

Action action = new Action(foo.DoSomething);

这就是分配的来源。没有完美的解决方法,但为了防止分配,您需要缓存和重用委托。

实现方面的修复

您可以通过为每个方法创建一个委托来在实现方面实现此目标。

public class Foo
{
    public void DoSomething() { /*nop*/ }

    private Action _doSomethingDelegate;
    public Action DoSomethingDelegate
    {
        get { return _doSomethingDelegate ?? (_doSomethingDelegate = DoSomething); }
    }
}

那么你只需要引用现有的代理,而不是方法。
Action action = foo.DoSomethingDelegate;

缓存修复

另一个选择是使用某种缓存类,但这会引入一堆对象生命周期问题,在游戏场景中可能不太合适。这是一种比较粗糙的实现,真正的实现可能需要使用弱引用。

public static class DelegateCache
{
    private static readonly Dictionary<object, Dictionary<string, Delegate>> Cache = new Dictionary<object, Dictionary<string, Delegate>>();

    private static Dictionary<string, Delegate> GetObjectCache(object instance)
    {
        Dictionary<string, Delegate> delegates;
        if (!Cache.TryGetValue(instance, out delegates))
        {
            Cache[instance] = delegates = new Dictionary<string, Delegate>();
        }
        return delegates;
    }

    public static T GetDelegate<T>(object instance, string method)
        where T: class
    {
        var delegates = GetObjectCache(instance);
        Delegate del;
        if (!delegates.TryGetValue(method, out del))
        {
            delegates[method] = del = Delegate.CreateDelegate(typeof(T), instance, method);
        }
        return del as T;
    }
}

使用它的方法如下:
Action action = DelegateCache.GetDelegate<Action>(foo, "DoSomething");

总结

经过测试,这两种方法每个对象/方法对仅有一个分配。尽管实现方案需要大量工作,但我可能会选择它,因为它更加简洁。如果有很多方法并且您计划添加许多方法,则可以使用T4生成带有委托实现的部分类。


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