如何以编程方式检测副作用(编译时或运行时)?
我有一个开始实现的缓存想法:
记忆功能和将返回值以及函数签名的哈希值存储在 Velocity 中。 使用 PostSharp,我想检查缓存并返回返回值的重新水化表示,而不是调用函数再次。 我想使用属性来控制这种行为。
不幸的是,这可能对我组织中的其他开发人员来说是危险的,如果他们爱上了性能增益并开始用缓存属性装饰每个可见的方法,包括一些带有副作用的方法。 当记忆库怀疑某个函数可能会导致副作用时,我想踢出编译器警告。
我如何判断代码可能会使用 CodeDom 或 Reflection 产生副作用?
I've got an idea for caching that I'm beginning to implement:
Memoizing functions and storing the return along with a hash of the function signature in Velocity. Using PostSharp, I want to check the cache and return a rehydrated representation of the return value instead of calling the function again. I want to use attributes to control this behavior.
Unfortunately, this could prove dangerous to other developers in my organization, if they fall in love with the performance gain and start decorating every method in sight with caching attributes, including some with side effects. I'd like to kick out a compiler warning when the memoization library suspects that a function may cause side effects.
How can I tell that code may cause side effects using CodeDom or Reflection?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
无论在实践还是理论上,这都是一个极其困难的问题。 我们正在认真思考如何针对您的场景预防或隔离副作用 - 记忆化,自动并行化等等——但这很困难,而且我们距离 C# 的可行解决方案还很远。 所以,没有承诺。 (如果你真的想消除副作用,请考虑切换到 Haskell。)
不幸的是,即使奇迹发生并且你找到了一种方法来防止记忆具有副作用的方法,你仍然会遇到一些大问题。 请考虑以下事项:
1) 如果您记忆一个本身调用记忆函数的函数会怎样? 这是一个很好的情况,对吧? 您希望能够编写记忆函数。 但记忆化有一个副作用:它将数据添加到缓存中! 因此,您立即遇到了一个元问题:您想要抑制副作用,但只是“坏”副作用。 你想要鼓励哪些“好的”,你想要阻止哪些“坏”的,而且很难区分它们。
2)对于异常情况你打算怎么办? 你能记住一个抛出异常的方法吗? 如果是这样,它总是抛出相同的异常,还是每次都抛出一个新的异常? 如果是前者,你打算怎么做? 如果是后者,现在您有一个记忆函数,它在两次不同的调用上有两个不同的结果,因为抛出了两个不同的异常。 异常可以被视为副作用; 例外情况很难驯服。
3) 对于没有副作用但仍然是不纯方法的方法,您打算怎么办? 假设您有一个方法 GetCurrentTime()。 这没有副作用; 调用不会改变任何内容。 但这仍然不是记忆化的候选者,因为需要任何两个调用才能产生不同的结果。 你不需要副作用检测器,你需要纯度检测器。
我认为你最好的选择是通过教育和代码审查来解决人类问题,而不是通过试图解决困难的技术问题。
This is an extremely hard problem, both in practice and in theory. We're thinking hard about ways to either prevent or isolate side effects for precisely your scenarios -- memoization, automatic parallelization, and so on -- but it's difficult and we are still far from a workable solution for C#. So, no promises. (Consider switching to Haskell if you really want to eliminate side effects.)
Unfortunately, even if a miracle happened and you found a way to prevent memoization of methods with side effects, you've still got some big problems. Consider the following:
1) What if you memoize a function that is itself calling a memoized function? That's a good situation to be in, right? You want to be able to compose memoized functions. But memoization has a side effect: it adds data to a cache! So immediately you have a meta-problem: you want to tame side effects, but only "bad" side effects. The "good" ones you want to encourage, the bad ones you want to prevent, and it is hard to tell them apart.
2) What are you going to do about exceptions? Can you memoize a method which throws an exception? If so, does it always throw the same exception, or does it throw a new exception every time? If the former, how are you going to do it? If the latter, now you have a memoized function which has two different results on two different calls because two different exceptions are thrown. Exceptions can be seen as a side effect; it is hard to tame exceptions.
3) What are you going to do about methods which do not have a side effect but are nevertheless impure methods? Suppose you have a method GetCurrentTime(). That doesn't have a side effect; nothing is mutated by the call. But this is still not a candidate for memoization because any two calls are required to produce different results. You don't need a side-effects detector, you need a purity detector.
I think your best bet is to solve the human problem via education and code reviews, rather than trying to solve the hard technical problem.
简单来说,CodeDom 或 Reflection 都做不到。
要准确确定某种方法是否会产生副作用,您必须了解它正在采取哪些操作。 对于.Net,这意味着破解IL 并以某种方式解释它。
Reflection 或 CodeDom 都不提供此功能。
Simply speaking you can't with either CodeDom or Reflection.
To accurately determine whether or not a method causes side effects you must understand what actions it is taking. For .Net that means cracking open the IL and interperting it in some manner.
Neither Reflection or CodeDom give you this capability.
反射本身并不能做到这一点,因为元数据没有任何这样的属性。
CodeDom 可能不够强大,无法检查所有 IL 指令。
因此,您必须使用反射 API 的非常低级的部分,以获取包含每个方法的原始 IL 的
byte[]
,并对其进行分析。 所以原则上这是可能的,但并不容易。您必须分析所有指令并观察它们有什么影响,以及这些影响是否会在某个重要范围之外继续存在(例如,它们是否修改了可以通过返回值或
out 泄漏的对象的字段)
参数,或者它们只是修改保证在方法外部无法访问的瞬态对象?)。听起来很复杂!
Reflection in itself won't do it, because the metadata doesn't have any such attributes.
CodeDom may not be powerful enough to inspect all IL instructions.
So you'd have to use the very low-level pieces of the reflection API that let you get a
byte[]
containing the raw IL of each method, and analyze that. So it's possible in principle, but not easy.You'd have to analyze all the instructions and observe what effects they have, and whether those effects are going to survive outside of some significant scope (e.g. do they modify the fields of objects that can leak out through return values or
out
parameters, or do they just modify transient objects that are guaranteed to be unreachable outside the method?).Sounds pretty complicated!