【问题标题】:Using Closures to keep track of a variable: Good idea or dirty trick?使用闭包来跟踪变量:好主意还是肮脏的把戏?
【发布时间】:2010-12-30 01:42:37
【问题描述】:

好的,我需要能够跟踪值类型对象,它们是另一个对象的属性,如果不让这些属性实现 IObservable 接口或类似接口,就无法做到这一点。然后我想到了闭包和 Jon Skeet 的著名例子,以及它如何打印出 9(或 10)次而不是数字的升序。所以我想为什么不这样做:

Class MyClass
{
    ...
    Func<MyValueType> 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<float> test = () => myFloat;
            Console.WriteLine(test());
            myFloat = 10;
            Console.WriteLine(test());
            Console.Read();
        }

    }
}

这将打印出5,然后是10

【问题讨论】:

  • 我已经编辑了我的答案;希望它对您的代码的适用性(或不适用性!)更加清晰。
  • 我个人认为您应该从问题中删除第一个示例;它实际上与您的问题无关。
  • 我想这是一个很好的观点。

标签: c# .net lambda closures value-type


【解决方案1】:

作为一种从属性中获取值的机制,它会起作用(但它不会提供任何及时发现更新的机制)。但是,这取决于您打算如何使用它。要做到这一点方便,您需要在代码中使用一堆 lambda,或者在运行时使用一些 DynamicMethod / Expression 代码。在大多数情况下,更类似于反射的东西会更方便。

我不一定会担心“值类型”方面;在大多数情况下,这不是瓶颈,尽管有 FUD - 使用 object 处理此类代码通常比通过泛型或类似方法容易得多。

我的 IDE 中有一些代码演示了 DynamicMethod 与原始反射(我打算在不久的将来写博客),展示了基于反射的代码如何不必很慢(或者只使用 HyperDescriptor) .

另一个选项是实现正确的接口/添加正确的事件。也许通过 PostSharp,也许通过动态类型(在运行时继承和覆盖),也许使用常规代码。

【讨论】:

    【解决方案2】:

    您需要将variable 成员键入为Func&lt;MyValueType&gt;(或另一个返回MyValueTypedelegate),但您无法以这种方式分配blah 的值。就像在上面的 foreach 循环中使用闭包一样,它只会在某个时间点进行评估。这不是使变量的值与其他对象保持同步的方法。事实上,没有任何一个都没有办法做到这一点:

    • 在某种循环中持续监控其他实例的属性值,例如Timer
    • 在另一个实例的类上实现更改通知事件

    您将能够实现这样的属性(因为在每次调用时都会评估属性),但是使用自定义委托的意义是什么,除了您不必知道任何关于其他实例。

    编辑

    我会尽量让这一点更清楚一点。使用您发布的此代码:

    Class MyClass
    {
        ...
        Action<MyValueType> variable;
        ...
        MyValueType blah = variable();
        //use it
        ...
    }
    ...
    MyClass myClass = new MyClass();
    myClass.variable = () => myOtherObject.MyOtherProperty;
    

    首先,要使其正常工作,variable 应该是 Func&lt;MyValueType&gt;,而不是 Action&lt;MyValueType&gt;Func 返回一个值,Action 没有;因为您正在尝试为变量赋值,你需要表达式返回一个值)。

    其次,你的方法的主要问题是——假设我正确地阅读了你的代码——你试图将实例变量 blah 的值分配给类中 variable() 的评估值宣言。这不起作用有几个原因:

    • 类声明中的赋值不能访问实例成员(variable 是)
    • 类声明中的变量赋值只发生在对象构造时。即使存在第一个条件,您也只会在实例化对象时得到一个NullReferenceException,因为它会尝试评估variable,当时是null
    • 即使忽略前两个,blah 的值仍将仅代表 variable() 在评估时的评估值。它不会“指向”该功能并自动保持同步,就像您正在尝试做的那样。

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

    【讨论】:

      【解决方案3】:

      您偶然发现了著名的公案:Closures are a poor man's object. 您正在使用Action&lt;T&gt; 替换T 类型的属性获取器。这样的事情在更动态的语言中(稍微)不是一个肮脏的把戏,因为它可以通过注入一个用你的日志记录函数装饰的 getter 来实现,但是在 C# 中没有一种优雅的方式来猴子修补某人的财产时他们没想到。

      【讨论】:

      • 我正在尝试推出一个库,让用户可以轻松地将类“焦点”放在一个对象上,所有的猴子补丁都是由用户完成的 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-21
      • 2014-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-26
      相关资源
      最近更新 更多