【问题标题】:Closure allocation and passing by reference闭包分配和引用传递
【发布时间】:2014-12-11 14:51:45
【问题描述】:

我有一个看起来像这样的函数:

private void OnItemChannelsListChanged(object sender, ListChangedEventArgs args)
{
...
int index = args.NewIndex;
this.UpdateLogsOnNewAssignedUnit(ref index, channels);
}

在哪里

private void UpdateLogsNewAssignedUnit(ref int index, List<IChannel> channels)
{
}

我使用了一个名为 Heap Allocations Viewer 插件(ps。我不隶属于它们),它在编辑器上直观地显示堆分配。好吧,在它下面的 ref int index 参数中显示

闭包分配:'index'、'channels'参数和'this'参考

据我了解,传递 int ref 不应创建新副本! 有谁知道为什么会发生这种情况,我可以做些什么来避免它?

在此先感谢

【问题讨论】:

  • 如果您要深入到那个级别,我会说使用 ILSpy 来查看创建闭包的位置。我在您的代码中看不到任何明显的关闭。也许它发生在UpdateLogsNewAssignedUnit 方法中?您是否无意中在那里创建了一个闭包?

标签: c# closures


【解决方案1】:

从这里

private void OnItemChannelsListChanged(object sender, ListChangedEventArgs args)

我认为你正在触发一个事件,你没有像你在第一行所说的那样“拥有一个功能”。

如果您从事件处理程序外部调用 UpdateLogsNewAssignedUnit 函数,我敢打赌分配不会发生。事件通常被实现为多播委托(它们不一定是,但通常是)。由于您是从事件处理程序调用它,因此编译器正在创建一个状态对象以在调用中使用。有关更多信息,请参阅this 文章。具体来说:

在引入 lambdas/匿名方法之前,想要调用接受委托的方法的 C# 开发人员通常需要定义一个类来存储他们的状态,向该类添加一个方法来进行处理,然后创建一个委托指向该状态实例上的该方法,例如

    public static void WriteOnPool(Stream stream, byte [] data) 
    { 
        var state = new WriteOnPoolState(); 
        state.stream = stream; 
        state.data = data; 
        ThreadPool.QueueUserWorkItem(new WaitCallback(state.Invoke)); 
    } 

    private sealed class WriteOnPoolState 
    { 
        public Stream stream; 
        public byte [] data; 

        public void Invoke(object ignored) 
        { 
            stream.Write(data, 0, data.Length); 
        }    
    }

现在在 C# 中,当您使用匿名方法或 lambda 时,编译器实际上最终会为您生成几乎相同的代码,因此您不再需要手动执行此操作。这是我之前使用匿名方法的示例生成的反编译版本:

    public static void WriteOnPool(Stream stream, byte[] data) 
    { 
        var locals2 = new DisplayClass1(); 
        locals2.stream = stream; 
        locals2.data = data; 
        ThreadPool.QueueUserWorkItem( 
            new WaitCallback(locals2.<WriteOnPool>b__0)); 
    } 

    [CompilerGenerated] 
    private sealed class DisplayClass1 
    { 
        public Stream stream; 
        public byte[] data; 

        public void <WriteOnPool>b__0(object param0) 
        { 
            this.stream.Write(this.data, 0, this.data.Length); 
        } 
    }

【讨论】:

  • 好吧,我知道在我的情况下,为 int 支付的代价很小,但假设说,最好避免创建从事件内部调用的新函数,而是继续编写事件中的逻辑?
  • 我不确定这是否有意义。调用者是否是事件处理程序真的很重要吗? OnItemChannelsListChanged 只是一个普通的方法。它不知道它是如何被调用的。我们看到的闭包是方法内部的,而不是外部的。 index 甚至不在图片中,直到我们已经在方法中。您的意思是 OnItemChannelsListChanged 发出的 IL 因调用者而异。这里真的是这样吗?是否根据调用方式生成了多个不同的OnItemChannelsListChanged 副本?听起来不对。
  • @Baldrick 是的;将事件处理程序的每个实例(每个 +=)视为委托的实例;因为该委托从类访问数据并通过“this”调用方法,所以编译器希望为数据创建一个状态对象。我没有工具可以确认是这种情况,但我认为这就是正在发生的事情。
  • @FatGiant 您能否确认如果您只是从控制台应用程序或单元测试中调用 UpdateLogsNewAssignedUnit,分配不会发生?
  • 我猜,在这一点上,这更有可能是堆分配工具的错误标志。 ReSharper 以前对我这样做过。正如@Baldrick 指出的那样,要做到这一点,编译器需要获取更多的状态信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-22
  • 1970-01-01
  • 2018-11-21
  • 1970-01-01
  • 2015-07-11
  • 2020-12-06
  • 1970-01-01
相关资源
最近更新 更多