有关面试中这样的问题,你认为最好的回答是什么?
我认为如果已经有类似的回答,请提供链接。
有关面试中这样的问题,你认为最好的回答是什么?
我认为如果已经有类似的回答,请提供链接。
另一种看待这个问题的方式是,不只是引用规范中指出结构体不能/没有析构函数。而是考虑如果规范被改变以允许结构体拥有析构函数会发生什么,或者更确切地说,让我们问一个问题:为什么语言设计者决定一开始就不允许结构体拥有“析构函数”?
(不要被这里的“析构函数”一词所困扰;我们基本上正在谈论一种在结构体上自动调用的魔术方法。换句话说,这是一种类似于C++析构函数的语言特性。)
首先要意识到的是,我们并不关心释放内存。无论对象是在堆栈上还是堆上(例如,在类中的结构体),内存最终总会被照顾到;通过弹出或收集等方式。拥有类似析构函数的真正原因是为了管理外部资源-像文件句柄、窗口句柄或其他需要特殊处理才能清理它们的东西,CLR本身并不知道。
现在假设允许结构体拥有可以进行此清理的析构函数。很好,直到你意识到当结构体作为参数传递时,它们会按值传递:它们被复制了。现在你有两个具有相同内部字段的结构体,它们都将尝试清理同一个对象。其中一个会先发生,因此之后使用另一个代码将开始出现神秘故障……然后它自己的清理也会失败(希望如此!-最坏情况是它可能成功地清理了一些其他随机资源-例如,在句柄值被重用的情况下可能会发生这种情况。)
你可以为作为参数的结构体做一个特殊情况,以便它们的“析构函数”不运行(但要小心-现在需要记住调用函数时始终是外部函数“拥有”实际资源-因此现在有些结构体与其他结构体略有不同……),但是对于普通结构体变量仍存在这个问题,其中一个可以分配给另一个从而创建副本。
你可以通过为赋值操作添加特殊机制来解决这个问题,该机制以某种方式允许新结构体与其新副本协商底层资源的所有权——也许它们共享它,或者从旧的转移所有权到新的——但现在你基本上已经进入了C++领域,需要复制构造函数、赋值运算符,并增加了一堆等待陷阱不知情的初学者程序员的微妙之处。请记住,C#的整个重点是尽可能避免那种C++风格的复杂性。
而且,为了让事情变得更加混乱,正如其他答案中指出的那样,结构体不仅存在于局部对象中。对于本地环境,作用域是良好定义的;但是结构体也可以成为类对象的成员。在这种情况下,“析构函数”何时被调用?当然,您可以在容器类最终完成时执行它;但现在您拥有了一个行为非常不同的机制,具体取决于结构体所在的位置:如果结构体是本地的,则在范围结束时立即触发它;如果结构体位于类内,则会懒惰地触发它...因此,如果您真的关心确保某些资源在您的一些结构体中在特定时间内清除,并且如果您的结构体最终可能成为类的成员,则可能需要像IDisposable/using()这样的显式内容来确保您已做好准备。
因此,虽然我不能代表语言设计者发言,但我可以猜测他们决定不包括此功能的一个原因是因为它会引起问题,而他们希望保持C#相对简单。
来自Jon Jagger的说法:
"结构体无法拥有析构函数。析构函数实际上只是一个伪装成object.Finalize
覆盖的方法,而结构体作为值类型,不受垃圾回收的控制。"
Finalize
方法等),以及其数据(意味着类型的所有实例字段的内容(公共,私有和受保护的混合,在派生类型字段之前出现基类字段)。因为每个堆对象都有一个标头,所以系统可以引用任何对象并知道它是什么,以及垃圾回收器应该对其执行什么操作。如果系统拥有所有已创建并具有Finalize
方法的对象列表,则可以检查列表中的每个对象,查看其Finalize
方法是否未被抑制,并适当地采取行动。
结构体存储时不包含任何头文件;像Point
这样具有两个整数字段的结构体只是简单地存储为两个整数。 尽管可以有一个指向结构体的ref
(当结构体作为ref
参数传递时会创建这样的东西),但使用ref
的代码必须知道ref
指向哪种类型的结构体,因为ref
和结构体本身都没有保存该信息。 此外,堆对象只能由垃圾回收器创建,垃圾回收器将保证任何创建的对象始终存在直到下一个GC周期。 相比之下,用户代码可以自行创建和销毁结构体(通常在堆栈上);如果代码创建了一个结构体以及一个对其的ref
,并将该ref
传递给被调用的例程,则在被调用的例程返回之前,该结构体无法被销毁(或执行任何操作),因此保证结构体至少存在直到被调用的例程退出。 另一方面,一旦被调用的例程退出,它所给出的ref
应被假定为无效,因为调用者随时可以自由地销毁结构体。
因为按照定义,析构函数用于销毁类的实例,而结构体是值类型。
参考:http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx
根据微软自己的说法:"析构函数用于销毁类的实例。"
所以问"为什么不能在(不是类的东西)上使用析构函数?"有点傻 ^^