使用闭包来跟踪变量:好主意或肮脏的技巧?
好吧,我需要能够跟踪作为另一个对象属性的值类型对象,如果没有这些属性实现IObservable接口或类似属性,则无法完成。 然后我想到了闭包和Jon Skeet的着名例子以及它如何打印出9次(或10次)而不是数字的升序。 所以我想为什么不这样做:
Class MyClass { ... Func variable; ... public void DoSomethingThatGetsCalledOften() { MyValueType blah = variable(); //error checking too not shown for brevity //use it } ... } ... in some other consuming code ... MyClass myClass = new MyClass(); myClass.variable = () => myOtherObject.MyOtherProperty; //then myClass will get the current value of the variable whenever it needs it
显然,这需要对闭包的工作原理有所了解,但我的问题是: 这是一个好主意还是一个肮脏的黑客和滥用封闭系统?
编辑:由于有些人似乎误解了我想说的内容,这里有一个控制台程序来演示它:
using System; using System.Linq; namespace Test { class Program { public static void Main() { float myFloat = 5; Func test = () => myFloat; Console.WriteLine(test()); myFloat = 10; Console.WriteLine(test()); Console.Read(); } } }
这将打印5
然后10
。
你偶然发现了着名的公案: 关闭是一个穷人的对象。 您正在使用Action
替换类型为T
的属性getter。 这样的事情在某种更动态的语言中会(稍微)少一些肮脏的技巧,因为它可以通过注入一个用日志记录function修饰的getter来实现,但是在C#中,没有一种优雅的方式来对某人的属性进行monkeypatch他们没想到。
作为获取财产价值的机制,它将起作用(但它不会提供任何及时通知更新的机制)。 但是,这取决于您打算如何使用它。 为方便起见,您需要在代码中使用一堆lambdas,或者在运行时使用一些DynamicMethod
/ Expression
代码。 在大多数情况下,更类似于reflection的东西会更方便。
我不一定会担心“价值类型”方面; 在大多数情况下,尽管有FUD,但这并不是瓶颈 – 使用object
处理此类代码通常比通过generics或类似处理更容易。
我的IDE中有一些代码,它们演示了DynamicMethod
与原始reflection(我打算很快在某一天写博客),展示基于reflection的代码不必太慢(或者只是使用HyperDescriptor
)。
另一种选择是实现正确的接口/添加正确的事件。 也许通过PostSharp,可能通过动态类型(在运行时inheritance和覆盖),也许通过常规代码。
您需要将variable
成员键入为Func
(或另一个返回MyValueType
delegate
),但您无法以这种方式分配blah
的值。 就像在上面的foreach
循环中使用闭包一样, 它只会在某个时间点进行评估 。 这不是保持变量值与其他对象同步的方法。 事实上,如果没有:
- 在某种循环中连续监视其他实例属性的值,如
Timer
- 在另一个实例的类上实现更改通知事件
您将能够实现这样的属性(因为在每次调用时都会对属性进行求值),但是使用自定义委托有什么意义,除了您不必了解其他实例的任何事实。
编辑
我会尽量让这个更清楚一些。 使用您发布的代码:
Class MyClass { ... Action variable; ... MyValueType blah = variable(); //use it ... } ... MyClass myClass = new MyClass(); myClass.variable = () => myOtherObject.MyOtherProperty;
首先,为了实现这一function, variable
应该是Func
,而不是Action
( Func
返回一个值, Action
没有;因为你试图为变量赋值,你需要返回一个表达式一个值)。
其次,您的方法的主要问题是 – 假设我正在正确读取您的代码 – 您试图将实例变量blah
的值分配给类声明中的variable()
的求值。 由于以下几个原因,这不起作用:
- 类声明中的赋值不能访问实例成员(哪个
variable
是) - 在构造对象时,只在类声明中赋值变量。 即使第一个条件存在,您只需在实例化对象时获得
NullReferenceException
,因为它会尝试评估variable
,当时该variable
为null
- 即使忽略前两个,
blah
的值仍然只代表variable()
在评估时的评估值。 它不会“指向”该function并自动保持同步,因为您似乎正在尝试这样做。
如果你不是在寻找某种自动同步,那么就没有什么可以阻止你只需保持Func
委托进行评估; 这种方法没有什么特别好或坏,除非委托(在你的情况下是lambda表达式)涉及使用局部变量,否则它不是闭包。