8

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());
    }
}

并用函数或值调用它。

4

7 回答 7

3

我怀疑 C# 会得到这个特性,但D有。您所概述的是在 C# 中实现惰性参数评估的合适方法,并且可能与lazyD 中的编译非常相似,并且使用更纯的函数式语言。

考虑到所有因素,四个额外的字符,加上可选的空格,对于正在成为一种多范式强类型语言的清晰的重载分辨率和表现力来说并不是一个特别大的代价。

于 2009-01-05T20:19:00.807 回答
2

编译器非常擅长推断类型,但不擅长推断意图。C# 3 中所有新语法糖的一个棘手问题是,它们可能会导致编译器对它们究竟做了什么感到困惑。

考虑你的例子:

() => "SomeText"

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

delegate {
    return "SomeText";
};

它是您发送PrintLine执行的这个匿名函数的委托。

它在过去一直很重要,但现在对于 LINQ、lambda、迭代器块、自动实现的属性等,使用.NET Reflector之类的工具在编译后查看代码至关重要看看是什么让这些功能真正起作用。

于 2009-01-05T19:41:51.180 回答
2

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

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

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

于 2011-08-13T13:49:13.943 回答
1

那么这两种说法是完全不同的。一个是定义函数,另一个是语句。混淆语法会更棘手。

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

"SomeText" //this is a string
于 2009-01-05T19:22:46.023 回答
1

您可以使用重载:-

public static void PrintLine(string text, TextWriter writer)
{
    PrintLine(() => text, writer);
}
于 2009-01-05T19:23:02.480 回答
1

您可以在 String 上编写一个扩展方法以将其粘贴。您应该能够编写“Some text”.PrintLine(Console.Out); 并让它为您完成工作。

奇怪的是,几周前我对 lambda 表达式进行了一些惰性求值,并在此处发表了博客

于 2009-01-05T19:23:19.517 回答
0

老实说,我并不完全理解你的问题,但你的解决方案对我来说似乎有点复杂。

我认为我使用 lambda 调用解决的一个问题是类似的,也许你可以以此为灵感:我想看看字典中是否存在一个键,如果不存在,我需要执行一个(昂贵的)加载操作。

public static class DictionaryHelper
{
    public static TValue GetValueOrLambdaDefault<TKey, TValue> (this IDictionary<TKey, TValue> dictionary, TKey key, Func<TValue> func)
    {
        if (dictionary.ContainsKey(key))
            return dictionary[key];
        else
            return func.Invoke();
    }
}

[TestClass]
public class DictionaryHelperTest
{
    [TestMethod]
    public void GetValueOrLambdaDefaultTest()
    {
        var dict = new Dictionary<int, string>();
        try
        {
            var res1 = dict.GetValueOrLambdaDefault(1, () => LoadObject());
            Assert.Fail("Exception should be thrown");
        }
        catch { /*Exception should be thrown*/ }

        dict.Add(1, "");
        try
        {
            var res1 = dict.GetValueOrLambdaDefault(1, () => LoadObject());
        }
        catch { Assert.Fail("Exception should not be thrown"); }
    }

    public static string LoadObject()
    {
        throw new Exception();
    }
}
于 2021-10-19T17:43:24.490 回答