C# 在这种情况下不调用GC.KeepAlive()安全吗?
假设我们在发布版本中运行了以下代码:C# 在这种情况下不调用GC.KeepAlive()安全吗?,c#,.net,C#,.net,假设我们在发布版本中运行了以下代码: class SomeClass { // This field is initialized somewhere in the constructor (not shown here). public SomeOtherClass Value; ... } class SomeOtherClass { ... public void Call() { ... } } ... static void Main() {
class SomeClass
{
// This field is initialized somewhere in the constructor (not shown here).
public SomeOtherClass Value;
...
}
class SomeOtherClass {
...
public void Call() {
...
}
}
...
static void Main()
{
SomeClass obj = new SomeClass();
SomeOtherMethod(obj.Value);
...
}
static void SomeOtherMethod(SomeOtherClass value) {
... // do sth else first
value.Call(); // value is a reference that points to `obj.Value` object in heap
}
我一直看到这样的代码,当时我不熟悉GC的工作原理。
但现在我知道,垃圾收集可能会在运行时检索到obj.Value之后,即甚至在调用SomeOtherMethod()之前,收集obj。因此,有一种可能性(虽然不太可能,但仍然可能)是当SomeOtherMethod
执行并且在操纵obj.Value
参数之前,obj
被标记为不可访问(因此它的成员Value
也被标记为不可访问),并且被GC收集并重新调整内存,因此,SomeOtherMethod
访问值
是非法的
因此,我认为解决方案是将GC.KeepAlive(obj)
或GC.KeepAlive(obj.Value)
添加为:
void MyMethod()
{
SomeClass obj = new SomeClass();
SomeOtherMethod(obj.Value);
...
GC.KeepAlive(obj); // or GC.KeepAlive(obj.Value);
}
但是我没有看到很多人(高级开发人员)这样做,代码总是正确运行,不会引发任何异常。那么,我的理解是否不正确,或者CLR对此有解决办法?当读取了
obj.Value
时,该值(可能是引用或原语/结构)的值会被复制(复制到不重要的位置;注意,在引用的情况下,复制的只是引用,而不是它引用的对象)。此时,是否收集obj
并不重要,值的副本是独立的
请注意,如果将ref返回属性或裸字段与SomeOtherMethod(ref obj.Value)
一起使用,则情况略有不同,但在这种情况下:我们仍然知道GC可以遵循托管指针(akaref
),如果需要,这将使对象保持活动状态
因此,不需要
GC.KeepAlive
。您需要GC.KeepAlive
的时间非常特殊(通常涉及终结器和数据之间的间接(非引用)关系);在大多数日常场景中:GC做你想做的事情,而不需要你的输入。GC系统的构建是为了在无法访问对象时收集对象。你不必帮助系统进行分析obj.Value
,一旦被读取,就不依赖于obj
的存在。@Damien_不信者,但是obj.Value
是指向堆中某个对象的引用,如果该对象是由GC收集的,我们如何继续调用其函数成员?您不需要像这样照看GC。如果您使用托管对象只是让它做它的工作,并假装它不存在(至少99%的时间)@amjad“但是obj.Value
是指向堆中对象的引用”,这是一个非常微妙的说法;obj.Value
是什么取决于您没有显示的obj.Value
obj.Value
可以只是一个int
;它可以是一个完全不同的对象,也可以是它本身(返回这个;
)-但这很好:GC知道如何处理引用;我同意ref obj.Value
可能是堆上对象内部的一个托管指针,但是:GC理解托管指针,所以同样:没有问题。正在收集的obj
不相关,或者不会收集obj
。“obj.Value是指向堆中某个对象的引用”-如果是,则是对其他对象的引用(无论Value
的类型如何,如果是引用类型)。它不是对obj
的(部分)引用。我添加了一些额外的代码来更好地解释它,如果obj
被收集,那么obj.Value
也被收集,那么SomeOtherMethod
无法执行?@amjad您的编辑不会改变任何东西:一旦obj.Value
被读取,您拥有该值的副本-您不再查看obj
中的值;注意我确实编辑了ref
,这可能允许您查看相同的值,但在这种情况下:您仍然不需要GC.KeepAlive
,因为ref
保留对象alive@amjad举个例子:你问我弗雷德的电话号码;我在手机上查了一下,然后告诉你;现在我的手机丢了/被偷了/坏了/被召回了/随便什么。这对你没有影响:你仍然有Fred的电话号码-信封背面潦草写的副本(或任何东西)甚至没有受到我的手机坏了这一事实的轻微影响。我真的很困惑,当我们传递参数时,如果参数类型是类,那么我们通过引用传递,所以不会有新的类实例作为副本创建,SomeOtherClass
是类,而不是类struct@amjad什么是“ok”是复杂的;我必须在一般情况下编写,在一般情况下,您不知道SomeOtherClass的终结器将要做什么-例如,它可以在执行清理后重置一些关键字段,这使得它在以后从SomeClass
的终结器访问时抛出异常,而在终结器中抛出异常意味着进程死亡;如果在终结器中使用Thread.Sleep
,则需要对其进行拍摄。基本上,我说的是“不要在雷区跳舞”,而你却在争论“如果发生了什么”——不:停止这样做;你(和我)会弄错的,你会输的。