自执行匿名函数

23

在 JavaScript 中,自执行函数是很常见的:

var i = (function(x) {
    return x;
})(42);

// i == 42

虽然我并不是在比较语言,但我认为这样的结构可以被翻译成C#,只要使用支持此结构的语言版本:

var i = (delegate(int x) {
    return x;
})(42);

或者:

var i = ((x) => {
    return x;
})(42);

甚至更多:

var i = (x => x)(42);

然而,每个版本都存在错误:

需要方法名称

自调用匿名方法是否不受支持(由于显式禁止或类型推断的不可能性),还是我的尝试存在错误?

我猜测因为没有方法声明(Func<T,T>),无法推断出类型,所以不能解决暗示的类型,认为我想按名称调用已声明的方法,并且语法出现了严重错误。


勘误

在这个问题被淹没之前:

var i = new Func<int, int>(x => x)(42);

我应该说我希望利用类型推断,但我想这可能不可能,因为它过于隐式。
所以,稍微澄清一下问题;我们知道我们可以使用var i = new Func<int, int>(x => x)(42);,但是在不创建或转换为Func的情况下,是否有可能实现?
用例
对于那些好奇(或担心)的人,用例类似于以下内容:
var o = new {
    PropertyA = () => {
        // value computation
    }(),
    PropertyB = () => {
        // value computation
    }(),
};

1
考虑为什么你需要在JavaScript中使用这个结构,并将该概念应用到C#中。 - asawyer
1
自执行函数在JavaScript中到底有什么作用? - asawyer
@asawyer 它允许你创建一个私有闭包,其中包含仅由从函数返回的对象访问的私有变量。使用返回的函数的代码无法访问这些私有变量。请参见此问题 - Brandon
1
@Brandon 我想重点在于这么做好不好。如果你编写一个_很长的_匿名函数,也许如果你编写一个_真正的_命名函数,你的代码会更清晰。 - Adriano Repetti
1
@Bracketworks,所以你需要一个可以包含私有变量值、可以公开属性和其他函数的对象?我认为在C#中有一个用于这种类型构造的词语... - asawyer
显示剩余10条评论
3个回答

20
var x = ((Func<int, int>)(y => y * 2))(10);
问题在于当编译器看到y => y * 2时,它默认将其归类为Expression而不是Func,除非有一些上下文信息让编译器知道它应该是一个Func。将其强制转换为Func可以为其提供所需的上下文。

5
var i = new Func<int, int>(x => x)(42); 的语法也是有效的。 - Caramiriel
1
@Caramiriel:啊,是的,我从来没有考虑过使用构造函数语法 :) - Brandon
如果确实是这种情况,即编译器将x => x视为表达式进行解释,那么为什么delegate的替代方法不起作用呢?(或者也被认为是表达式了吗? - Dan Lugg
1
@Bracketworks:我认为这是因为使用delegate声明的匿名方法在分配给某个东西之前不是完全类型化的。再次需要上下文。这个可以工作:((Func<int, int>)delegate(int y) { return y * 2; })(10);。请参阅MSDN - Brandon

11

我想C#团队的某个人可以给出更好的答案,但我也尝试回答一下。问题不在于是否是最佳实践,而在于是否可能以及如果不可能,为什么不行。

让我们从你会写什么开始:

var i = (x => x)(42);

非常简单,你将一个整数(42)传递给lambda,它对其进行操作并返回相同的值。它的参数是一个整数(从其使用中推断出),它返回一个整数(从表达式推断出),而i是另一个整数(从返回值推断出)。
不幸的是,在第一步中就出了问题。让我引用cite big Jon的话:

编译器试图根据使用lambda表达式的上下文来确定其参数的类型。

这是什么意思?实际上,编译器需要一些信息,但它只能使用:
  • 显式转换、构造函数或声明,例如(Func<int, int>)(x => x))new Func<int, int>(x => x)。这里所需的类型是通过所需的最终类型给出或推断出的。
  • 赋值(再次因为可以推断出最终类型),例如:Func<int, int> f = (x => x);
在您的情况下,编译器必须从其参数中推断函数类型,但这是不可能的,因为参数是根据表达式验证的,而不是反过来。为什么C#不允许这种情况呢?因为(x => x)只是一个委托,它可以是任何接受整数参数的委托(如果我们假设编译器可以从rhs中推断lhs并根据lhs验证rhs),并返回另一个整数。实际上,在C#中不允许委托之间的转换,因此该表达式无效(即使这个“特殊”的委托不会被分配给任何变量并且不会在将来使用)。它可以是Func或Expression>或任何其他委托(对于bool参数,甚至可能是Predicate)。当然,这是C#设计的决定,例如在VB.NET中,同样的表达式是完全有效的:
 Dim i = (Function(x) x)(42)

不同的语言有不同的规则需要遵守,C# 的目标就是避免这种模糊性。

1

我使用重载的静态方法来解决语言限制,该方法接受匿名函数并执行它:

namespace Utils {
    public static class InvokeInlineHelper {
        public static T Invoke<T>(Func<T> f) {
            return f();
        }

        public static T Invoke<A1, T>(Func<A1, T> f, A1 a1) {
            return f(a1);
        }

        //...overloads with more than 1 argument
    }
}

使用起来非常简单并且保证了类型安全。这是我的Program.cs文件:

global using static Utils.InvokeInlineHelper;

const string prefix = "Value";

var o = new
{
    PropertyA = Invoke(() => {
        return prefix + "A";
    }),
    PropertyB = Invoke((input) => {
        return prefix + input;
    }, "B"),
};

Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(o));
//Outputs: {"PropertyA":"ValueA","PropertyB":"ValueB"}

请注意全局静态使用,这可以改善语法。当然,闭包仍然可以被利用,如使用前缀变量所示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接