我想编写脚本引擎并使用结构而不是类来存储变量数据,以避免在创建新类实例时涉及的内存分配。
我的第一个问题是:在创建结构时是否还有内存分配,或者它是在某种堆栈上创建的(即实际堆栈还是预先分配的本地堆叠的内存区域)?
我的第二个问题是:假设一个结构将其某些方法作为事件处理程序附加到其他对象的事件上。由于没有析构函数,当结构被销毁时,如何取消绑定这些事件处理程序?我是否必须手动操作,或者我被迫使用课程?我不想为值类型数据(如整数)分配内存。
答案 0 :(得分:2)
不要将值类型用于他们所谓的效率。如果设计中的某些内容不是值类型,请不要将其设置为值类型以获得一些CPU周期。
我想[...]使用结构而不是类[以]避免在创建新类实例时涉及的内存分配。
我认为你的意思是"动态内存分配",因为肯定会为每个struct
分配一些内存。
根据具体情况,可能会在使用struct
的同时进行动态内存分配。具体而言,当您将struct
打包为object
时会发生这种情况。
在创建
struct
时是否还有内存分配,或者是在某种堆栈上创建的?
如果这是一个局部变量,则在堆栈上分配值类型的内存,或者如果它是另一个struct
或class
内的字段,则在托管该值的类内部分配。 / p>
它的一些方法作为事件处理程序附加到其他对象的事件上。由于没有析构函数,如何在销毁结构时取消绑定事件处理程序?
让我们不要超越自己,首先考虑如何将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
事件处理程序时,X
内的Bar
会按预期进行修改。但是看看当你检查X
b
的实际值时会发生什么,你创建了一个委托:它的值保持为零! (demo 1)
一旦您Bar
成为一个班级(demo 2),此行为就会发生变化。
这个故事的寓意是你应该非常小心价值类型,特别是在需要捕获的情境中,例如制作代表。如果您在局部变量上创建委托,则知道该变量的范围在何处结束,因此您可以正确处置该委托:
Bar b = new Bar();
try {
someClass.SomeEvent += b.DoIt();
} finally {
someClass.SomeEvent -= b.DoIt();
}
但是,如果委托将变量的范围扩展到本地以外,则行为将出乎意料并且令人困惑。
答案 1 :(得分:1)
没有相应的终结器,但struct
仍然可以实现IDisposable
。坦率地说,我认为这样做是不正确和令人困惑的,仅仅因为生命周期是如此不同。例如:
var x = new SomeStruct(); // fine
var y = x; // hmm....
现在我们有两个副本的结构。如果你已经完成了"使用x
,这是否意味着您已完成"与y
?身份和拳击也存在巨大问题 - 因为事件跟踪目标对象。坦率地说,做任何复杂的涉及结构作为事件订阅者是非常有问题的,除非所有内容同时超出范围。举个例子:这会写Hi
两次,因为取消订阅是一个不同的框 - 所以基本上取消订阅失败了:
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");
}