【问题标题】:Forced Garbage collection or reflection to a Private field, which is less evil?强制垃圾收集或反射到私有字段,哪个更不邪恶?
【发布时间】:2011-05-21 14:43:19
【问题描述】:

我们有一个第三方库,它在内部使用 SafeHandle 来处理非托管资源。在某些错误情况下,有必要处理对象并重新创建它。但是,在 dispose 实现中存在一个错误,该错误会阻止 Handle 在这些情况的子集中关闭。这会阻止新对象在其终结器运行之前成功创建。

已经提出了两种解决方案(都是邪恶的)来解决这个问题,直到我们可以修复第三方代码:

  1. 运行GC.Collect 以使终结器运行并清理对象

  2. 使用反射获取 Handle 并在 dispose 失败时将其关闭

其中哪一个不那么邪恶,为什么? 有没有其他我们没有考虑过的方法比这两种方法更邪恶?

【问题讨论】:

  • 反射,我会说,除非代码可以被认为很可能会改变其内部实现。

标签: c# .net reflection garbage-collection private-members


【解决方案1】:

我赞成私人反思。这是一个本地化的错误,所以解决方案也应该是本地的。你的代码打算做什么更清楚了。而且您可能可以添加一些测试,一旦错误得到修复就会注意到。因此,一旦不再需要该 hack,就可以轻松删除它。

...
thirdPartyObject.Dispose();
ThirdPartyDisposeBugWorkaround(thirdPartyObject);
...

void ThirdPartyDisposeBugWorkaround(ThirdPartyClass thirdPartyObject)
{
   //Do private reflection here
}

另一方面,强制 GC 具有全局效果。干扰 GC 的原因有很多(其中大部分是不好的)。您的代码的作用不太明显。因此,即使修复了错误,也可能会保留调用。

Old New Thing: Don't use global state to manage a local problem

【讨论】:

  • 一千次同意。致电GC.Collect 几乎总是错误的做法。以及链接到 Raymond 博客的奖励积分。但无论您决定如何,重要的是广泛记录它
【解决方案2】:

我会进行反思,但请确保您对其进行了错误处理,以明确指出问题所在,请记住,该错误可能要到几年后才会触发,并且您的开发团队可能会翻身而没有人记得这个古怪的黑客。

try
{
   .. hacky reflection ..
}
catch(Exception ex)
{
    throw new Exception("Reflection on private field 'Xyz' of 3rd Party Component 'Abc' failed.  Was 'Abc' updated? Reflection is used due to bug in 'Dispose' implementation.", ex);
}

【讨论】:

  • 我可能会使用Debug.Assert。如果我要抛出异常,我肯定不会抛出System.Exception。也许InvalidOperationException
  • @Cody Gray,我们在内部使用了一个我们称为ExceptionUtil.Rethrow 的帮助器类,它产生了一个与原始类型相同的新异常,在添加更多信息时很有用。我当然不想使用Debug.Assert,因为如果可以独立于应用程序更新第 3 方库(这当然是一个可怕的想法,但人们会做愚蠢的事情),那么这是生产中会发生的异常。
  • 我只是不确定您是否要在内存泄漏的情况下抛出异常。调试断言似乎已经足够好了。是的,这是一个可怕的想法。如果有人这样做,他们就创建了一个错误。更好的是,首先阻止他们这样做:将您的应用程序的构建绑定到第三方 DLL 的特定版本。无论如何,这是一个好主意,因为您依赖于明确无证的行为。而且,我真的不知道您的助手类与任何事情有什么关系?我的意思是,我想我感觉更好,因为你不写错误的代码抛出基 Exception 类... ;-)
  • @Cody Gray,当发生内存泄漏时,这不会是一个例外。当你使用反射调用一个不存在的属性时,你会得到一个异常。那就是抓住它并重新抛出更多信息。不过,我想得越多,您确实可以首先通过空检查来避免异常-尝试获取该字段,如果不存在,则抛出异常或任何适合应用程序的异常,然后建筑学。无论哪种方式,概念都是一样的。
【解决方案3】:

首先选择一个有效的。如果他们都工作,则选择对系统影响最小的那个。 Gc.Collect 在整个应用程序中更像是一把锤子。您的反射代码很脆弱,但影响应该很小。

【讨论】:

  • GC.Collect 绝对有效(尽管这显然不能保证),这意味着反射代码应该有效,因为 GC.Collect 除了导致终结器运行之外不会做任何神奇的事情。跨度>
【解决方案4】:

如果它没有被标记为密封,你可以继承它并实现你自己的 dispose。至于反射与 GC,我肯定会使用反射。正如其他人所说,GC 可能无法按预期工作。它可以进行集合迭代,但实际上不会释放您的句柄。

我想指出:如果其他东西仍然引用此 SafeHandle,并且您释放它,您很容易将其他错误引入您的系统。

【讨论】:

  • 我认为基于在反射器中查看大量有问题的代码是安全的......但我意识到这种风险。我不认为 dispose 覆盖会起作用,因为有问题的字段是私有的,不受保护。
  • 我打算让您使用继承作为放置反射代码的地方。这样你的独特情况就会被抽象到它所属的地方。这是在处置过程中。
  • 这是一个非常好的主意,我必须弄清楚如何实现它。唯一的复杂性是“dispose”实际上是一个非虚拟成员函数,其行为类似于 dispose...,这在整个问题的上下文中非常小。
【解决方案5】:

将“CodeInChaos”论据向前推进,为什么不要求在特定代进行收集。

GC.GetGeneration(Object obj) 将返回对象所在的代和GC.Collect(Int32 gen)

例如:

Int32 generation = GC.GetGeneration(theObject);
theObject = null;
GC.Collect(generation);
GC.WaitForPendingFinalizers();
GC.Collect(generation); // this is req because first collect puts thisObject on freachable queue not // garbaged yet.

【讨论】:

  • 不,绝对不要这样做。这与 CodeInChaos 提出的论点完全矛盾。
  • 在大多数情况下是的。但在他的情况下,这(GC 控制)是一种选择。我指的是 CodeInChaos 的“它是一个本地化的错误”声明。所以我没有进行全局收集,而是针对特定一代的收集。如果原因是为了提高性能,不建议调用 GC.Collect() 和重载,但在像他这样的例外情况下,这并不是一个糟糕的选择,只有在代码准备好处理对象时才会发生。
  • 不,这仍然是不明智的。这仍然是针对局部问题的全局解决方案。这只是一个影响较少全局对象的全局解决方案。这仍然与 CodeInChaos 的建议相矛盾。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-12
  • 1970-01-01
  • 2010-09-16
  • 2012-07-08
  • 1970-01-01
  • 1970-01-01
  • 2018-12-30
相关资源
最近更新 更多