使用闭包来跟踪变量:好主意或肮脏的技巧?

好吧,我需要能够跟踪作为另一个对象属性的值类型对象,如果没有这些属性实现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 ,而不是ActionFunc返回一个值, Action没有;因为你试图为变量赋值,你需要返回一个表达式一个值)。

其次,您的方法的主要问题是 – 假设我正在正确读取您的代码 – 您试图将实例变量blah的值分配给类声明中的variable()的求值。 由于以下几个原因,这不起作用:

  • 类声明中的赋值不能访问实例成员(哪个variable是)
  • 在构造对象时,只在类声明中赋值变量。 即使第一个条件存在,您只需在实例化对象时获得NullReferenceException ,因为它会尝试评估variable ,当时该variablenull
  • 即使忽略前两个, blah的值仍然只代表variable()在评估时的评估值。 它不会“指向”该function并自动保持同步,因为您似乎正在尝试这样做。

如果你不是在寻找某种自动同步,那么就没有什么可以阻止你只需保持Func委托进行评估; 这种方法没有什么特别好或坏,除非委托(在你的情况下是lambda表达式)涉及使用局部变量,否则它不是闭包。