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

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

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

不幸的是,如果他们爱上了性能提升并开始使用缓存属性(包括一些带有副作 当memoization库怀疑某个函数可能会导致副作用时,我想发出一个编译器警告。

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

无论是在实践中还是在理论上,这都是一个极其困难的问题。 我们正在考虑如何预防或隔离精确场景的副作用 – 记忆, 自动并行化等等 – 但这很困难,而且我们仍然远不是C#的可行解决方案。 所以,没有承诺。 (如果你真的想要消除副作用,请考虑切换到Haskell。)

不幸的是,即使奇迹发生了,你找到了一种方法来防止有副作用的方法的记忆,你仍然有一些问题。 考虑以下:

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

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

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

我认为你最好的办法是通过教育和代码审查解决人类问题,而不是试图解决棘手的技术问题。

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

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

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

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

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

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

因此,您必须使用reflectionAPI的非常低级的部分,它们可以让您获得包含每种方法的原始IL的byte[] ,并对其进行分析。 所以它原则上是可能的,但并不容易。

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

听起来很复杂!