如何以编程方式检测副作用(编译时间或运行时间)?

时间:2009-07-28 15:25:38

标签: c# memoization side-effects

我有一个关于缓存的想法,我已经开始实施了:

Memoizing function并将返回与Velocity中的函数签名的散列一起存储。使用PostSharp,我想检查缓存并返回返回值的重新水合表示,而不是再次调用该函数。我想使用属性来控制这种行为。

不幸的是,这可能对我组织中的其他开发人员造成危险,如果他们爱上了性能提升并开始使用缓存属性来装饰每个方法,包括一些带有副作用的方法。当memoization库怀疑某个函数可能会导致副作用时,我想发出一个编译器警告。

如何判断代码可能会导致使用CodeDom或Reflection产生副作用?

3 个答案:

答案 0 :(得分:8)

这在实践和理论上都是一个极其困难的问题。我们正在考虑如何防止或隔离副作用以准确地处理您的场景 - 记忆,automatic parallelization等等 - 但这很困难,而且我们仍然远不是C#的可行解决方案。所以,没有承诺。 (如果你真的想消除副作用,请考虑切换到Haskell。)

不幸的是,即使奇迹发生并且您找到了一种方法来防止带有副作用的方法的备忘录,您仍然会遇到一些问题。请考虑以下事项:

1)如果你记住一个本身正在调用memoized函数的函数怎么办?这是一个很好的情况,对吗?您希望能够撰写记忆功能。但是memoization有副作用:它将数据添加到缓存!所以你马上就会遇到一个元问题:你想要驯服副作用,但只是“坏”的副作用。你想要鼓励的“好”,你想要阻止的坏的,并且很难区分它们。

2)你打算怎么做异常?你能记住一个抛出异常的方法吗?如果是这样,它是否总是抛出相同的异常,或者每次都抛出一个新的异常?如果是前者,你打算怎么做?如果是后者,现在你有一个memoized函数,它在两个不同的调用上有两个不同的结果,因为抛出了两个不同的异常。例外可被视为副作用;很难驯服例外。

3)对于没有副作用但仍然不纯方法的方法,您打算做什么?假设您有一个GetCurrentTime()方法。这没有副作用;呼叫没有变异。但这仍然不是备忘录的候选者,因为任何两个调用必需来产生不同的结果。 您不需要副作用检测器,需要纯度检测器。

我认为您最好的办法是通过教育和代码审核来解决人类问题,而不是尝试解决难以解决的技术问题。

答案 1 :(得分:0)

简单地说,你无法使用CodeDom或Reflection。

要准确判断某种方法是否会产生副作用,您必须了解其所采取的措施。对于.Net来说,这意味着打开IL并以某种方式对其进行交互。

Reflection或CodeDom都没有为您提供此功能。

  • CodeDom是一种在应用程序中生成代码的方法,只有非常有限的检查功能。它基本上局限于各种解析版本所理解的语言子集。
  • 思考的优势在于它能够检查元数据而不是方法体的基础IL。 MetaData只能为您提供一组非常有限的信息,包括哪些内容和不起副作用。

答案 2 :(得分:0)

反射本身不会这样做,因为元数据没有任何这样的属性。

CodeDom可能不够强大,无法检查所有IL指令。

所以你必须使用反射API的非常低级的部分,让你得到一个byte[]包含每个方法的原始IL,然后分析它。所以它原则上是可能的,但并不容易。

您必须分析所有指令并观察它们具有什么效果,以及这些效果是否会在某个重要范围之外生存(例如,它们是否会修改可能通过返回值泄漏的对象的字段或{ {1}}参数,还是仅修改保证在方法外无法访问的瞬态对象?)。

听起来很复杂!