C# - 取消绑定结构 "destruction" 上的事件

C# - Unbinding events on struct "destruction"

我想编写一个脚本引擎并使用结构而不是 classes 来存储变量的数据,以避免在创建新的 class 实例时涉及内存分配。

我的第一个问题是:在创建结构时是否也有内存分配,或者它是在某种堆栈上创建的(即实际堆栈或局部堆栈的预分配内存区域)?

我的第二个问题是:想象一个结构有一些方法作为事件处理程序附加到其他对象的事件上。由于没有析构函数,当结构被销毁时,如何取消绑定这些事件处理程序?我是否必须手动执行此操作,还是被迫使用 class 代替?我不想为整数等值类型数据分配内存。

没有终结器的等价物,但是 struct 仍然可以实现 IDisposable。不过,坦率地说,我认为这样做是不正确和令人困惑的,仅仅是因为生命周期是如此不同。例如:

var x = new SomeStruct(); // fine
var y = x; // hmm....

现在我们有结构的两个副本。如果您 "done" 和 x,是否意味着您 "done" 和 y?身份和装箱也存在巨大问题——因为事件跟踪目标对象。坦率地说,做任何涉及结构作为事件订阅者的复杂事情都是有很大问题的,除非所有事情都同时超出范围。举个例子:这写 Hi twice 因为取消订阅是不同的 box - 所以基本上取消订阅失败:

using System;

static class Program
{
    static void Main()
    {
        var x = new Foo();
        TheThing += x.Bar;
        TheThing?.Invoke();
        TheThing -= x.Bar;
        TheThing?.Invoke();
    }
    static event Action TheThing;
}
struct Foo
{
    public void Bar() => Console.WriteLine("Hi");
}

不要为了它们所谓的效率而使用值类型。如果某些东西在设计上不是值类型,请不要将其设为值类型以获得几个 CPU 周期。

I would like to [...] use structures instead of classes [to] avoid the memory allocation(s) involved when creating a new class instance.

我认为你的意思是 "dynamic memory allocation",因为肯定有一些内存分配给每个 struct

根据情况,可能会结合使用 struct 进行动态内存分配。具体来说,当您将 struct 装箱为 object.

时,就会发生这种情况

is there also a memory allocation when creating a struct, or it is created on some kind of stack?

如果这是一个局部变量,则值类型的内存分配在堆栈上,或者如果它是另一个 structclass.

some of its methods attached as event handlers onto events of other objects. How do I unbind event handlers when the struct is destroyed since there is no destructor?

让我们先不着急,首先考虑如何将 struct 的方法作为处理程序附加到其他对象的事件。当您执行附件时,您的 struct 将被装箱以捕获到事件处理程序的闭包中,这可能会造成很大的混乱。

考虑这个例子:

delegate void Foo();

struct Bar {
    public int X;
    public void DoIt() {
        X++;
        Console.WriteLine("X={0}", X);
    }
}
public static void Main() {
    Bar b = new Bar();
    var foo = new Foo(b.DoIt); // Watch out!
    foo();
    foo();
    foo();
    Console.WriteLine("X={0} What???", b.X);
}

当您调用 foo 事件处理程序时,Bar 中的 X 会按预期进行修改。但是看看当您检查 bX 的实际值时会发生什么,您从中创建了一个委托:它的值仍然为零! (demo 1)

一旦您将 Bar 设为 class (demo 2).

,此行为就会改变

这个故事的寓意是您应该非常小心地对待值类型,尤其是在需要捕获的情况下,例如创建委托。如果你在一个局部变量上创建一个委托,你就知道该变量的范围在哪里结束,所以你可以正确地处理该委托:

Bar b = new Bar();
try {
    someClass.SomeEvent += b.DoIt();
} finally {
    someClass.SomeEvent -= b.DoIt();    
}

但是,如果委托将变量的范围扩展到本地之外,行为将是意想不到的和令人困惑的。