C#lambda表达式和惰性求值

时间:2009-01-05 19:18:40

标签: c#-3.0 lambda lazy-evaluation

lambda表达式的一个优点是,只有在需要结果时才需要计算函数。

在下面(简单)示例中,仅在编写器存在时才评估文本函数:

public static void PrintLine(Func<string> text, TextWriter writer)
{
    if (writer != null)
    {
        writer.WriteLine(text());
    }
}

不幸的是,这使得使用代码有点难看。您无法使用常量或变量(如

)调用它
PrintLine("Some text", Console.Out);

并且必须这样称呼它:

PrintLine(() => "Some text", Console.Out);

编译器无法从传递的常量中“推断”无参数函数。有没有计划在未来的C#版本中改进这一点,还是我错过了什么?

更新:

我自己发现了一个肮脏的黑客:

    public class F<T>
    {
       private readonly T value;
       private readonly Func<T> func;

       public F(T value) { this.value = value; }
       public F(Func<T> func) {this.func = func; }

       public static implicit operator F<T>(T value)
       {
            return new F<T>(value);
       }

       public static implicit operator F<T>(Func<T> func)
       {
           return new F<T>(func);
       }

       public T Eval()
       {
           return this.func != null ? this.func() : this.value;
       }
}

现在我可以将函数定义为:

public static void PrintLine(F<string> text, TextWriter writer)
{
    if (writer != null)
    {
        writer.WriteLine(text.Eval());
    }
}

并使用函数或值调用它。

6 个答案:

答案 0 :(得分:3)

您可以使用重载: -

public static void PrintLine(string text, TextWriter writer)
{
    PrintLine(() => text, writer);
}

答案 1 :(得分:3)

我怀疑C#会获得此功能,但D拥有它。您所概述的是在C#中实现延迟参数评估的合适方法,并且可能与D中的lazy以及更纯粹的函数式语言编译非常相似。

考虑到所有因素,四个额外的字符,加上可选的空格,对于明确的重载分辨率和表达性而言,并不是一个特别大的代价,而这正是一种多范式的强类型语言。

答案 2 :(得分:2)

不幸的是,你在C#中拥有丑陋的语法。

更新中的“脏黑客”不起作用,因为它不会延迟对字符串参数的评估:它们会在传递给operator F<T>(T value)之前得到评估。

PrintLine(() => string.Join(", ", names), myWriter)PrintLine(string.Join(", ", names), myWriter)进行比较在第一种情况下,字符串仅在打印时才会连接;在第二种情况下,无论如何都将字符串连接起来:只有打印是有条件的。换句话说,评估并不是懒惰。

答案 3 :(得分:1)

这两个陈述完全不同。一个是定义一个函数,另一个是声明。混淆语法会更加棘手。

() => "SomeText" //this is a function

"SomeText" //this is a string

答案 4 :(得分:1)

你可以在String上编写一个扩展方法来粘贴它。你应该可以编写“Some text”.PrintLine(Console.Out);并让它为你工作。

奇怪的是,几周前我做了一些关于lambda表达式的惰性评估和blogged about it here

答案 5 :(得分:1)

编译器非常擅长推断类型,它不擅长推断 intent 。关于C#3中所有新语法糖的一个棘手问题是,它们可能会导致混淆编译器对它们的作用。

考虑你的例子:

() => "SomeText"

编译器看到了这一点并理解您打算创建一个不带参数的匿名函数并返回一种System.String。这是从你给它的lambda表达式推断出来的。实际上你的lambda被编译为:

delegate {
    return "SomeText";
};

并且它是您要发送到PrintLine执行此匿名函数的委托。

过去一直很重要但现在使用LINQ,lambdas,迭代器块,自动实现的属性,除了其他功能之外,使用像.NET Reflector这样的工具来查看你的内容至关重要编译后的代码,看看是什么让这些功能真正起作用。