C#关闭是否支持非本地回报?

这里解释一下这个问题是一些Scala代码:

object Scratch { def foo: Int = { val list = List(1, 2, 3, 4) list.foreach { each => if(each > 2) { return each } println(each) } return 5 } def main(args : Array[String]) : Unit = { val i = foo println("i: " + i) } 

上面的代码打印到控制台:

  1
 2
我:3 

特别要注意的是, list.foreach使用的闭包有一个return语句,而这个return语句导致foolist.foreach的调用者)返回,中断foreach枚举并提供foo的实际返回值。 这个return没有在foo方法本身中声明,所以是“非本地返回”。

现在的问题是如何在C#中发生同样的事情,例如将其他东西打印到控制台? 问题是关于C#中的非本地返回。 只有在支持相同的输出时才会出现在上面的代码中。

注意:这不是Scala的特色。 其他语言也可以像Smalltalk或Kotlin一样,也可以是Ruby和其他语言。

我想自己尝试一下,但下载9 GB的安装Visual Studio 2017后,我被告知升级到Windows 10,这不是我想做的只是为了得到这个问题的答案。

不,C#不支持关闭中的非本地回报。 AC#闭包本身就是一种方法,并不会与其封闭的方法共享上下文(捕获的变量除外)。 当你从一个lambda表达式返回时,你从这个方法返回,即lambda引用的匿名方法。 它不影响声明lambda的方法,也不影响lambda调用的方法(如果与声明lambda不同的方法)。

我对Scala和Ruby都不太熟悉,但看起来Scala与Ruby相比更像C#。 如果是这样,我认为非本地返回导致调用方法返回。 这只是在你的例子中发生的调用方法是相同的声明方法,但由于显而易见的原因,lambda导致声明方法返回将是非常奇怪的。 也就是说,在声明方法已经返回之后,lambda可能会被调用。 在堆栈溢出问题上有更深入的Ruby讨论(通过推论,Scala) Ruby的代码块是否与C#的lambda表达式相同? 。

当然,你仍然可以在C#中完成相同的效果。 只是你正在使用的确切语法不会这样做。 在.NET中, List<T>泛型类有一个ForEach()方法,所以从字面上看你的代码示例(即使用内置的ForEach()方法),这是最接近C#

 static void Main(string[] args) { var i = foo(); WriteLine($"i: {i}"); } static int foo() { var list = new List<int> { 1, 2, 3, 4 }; try { list.ForEach(each => { if (each > 2) { throw new LocalReturnException(each); } WriteLine(each); }); } catch (LocalReturnException e) { return e.Value; } return 5; } class LocalReturnException : Exception { public int Value { get; } public LocalReturnException(int value) { Value = value; } } 

因为List<T>.ForEach()方法没有提供任何机制来中断源enumerable的枚举,所以获得方法提前返回的唯一方法是通过抛出异常绕过正常的方法返回机制。

当然,例外是相当重的。 对于try / catch处理程序来说,有一个边际成本,而实际上投掷和捕获成本是非常昂贵的。 如果你需要这个习惯用法,最好是创建自己的枚举方法,它提供了一个机制来中断枚举并返回一个值。 例如,创建扩展方法如下所示:

 public static T? InterruptableForEach<T>(this IEnumerable<T> source, Func<T, T?> action) where T : struct { foreach (T t in source) { T? result = action(t); if (result != null) return result; } return null; } public static T InterruptableForEach<T>(this IEnumerable<T> source, Func<T, T> action) where T : class { foreach (T t in source) { T result = action(t); if (result != null) return result; } return null; } 

第一个是你的例子所需要的。 我展示了两个,因为当涉及到null值时,C#将像int这样的值类型与引用类型区别开来,但是第二个在这里并不是必须的。

用扩展方法,你可以做这样的事情:

 static int foo() { var list = new List<int> { 1, 2, 3, 4 }; var result = list.InterruptableForEach(each => { if (each > 2) { return each; } WriteLine(each); return null; }); return result ?? 5; } 

请注意,调用者需要配合lambda和扩展方法。 也就是说,扩展方法是明确地报告lambda本身返回的内容,以便知道lambda是否过早返回,如果是,那么值是什么。

一方面,这比Scala版本更笨拙和冗长。 另一方面,它符合C#的显式性和表达性的趋势,避免了模棱两可的情况(例如,如果foo()方法没有返回int ,但是lambda做了什么?)。

这个答案显示了另一种可能的方法。 我个人更喜欢上述两者之一,因为它们实际上中断了枚举,而不是直接跳过主要的lambda体,直到枚举结束(这可能是无限枚举的问题),并且不引入额外的捕获该答案所需的变量。 但它在你的例子中工作。

这是我对你的问题的答案:

1)你不需要安装任何东西与C#和LINQ arround。 你可以简单地使用https://dotnetfiddle.net

2)这是我的代码https://dotnetfiddle.net/3V8vBj

 using System.Collections.Generic; public class Program { static int foo() { var list = new List<int> { 1, 2, 3, 4 }; int ret = 5; bool keepGoing = true; list.ForEach(each => { if (!keepGoing) return; if (each>2) { ret=each; keepGoing = false; return; } System.Console.WriteLine(each); }); return ret; } public static void Main(string[] args) { var i = foo(); System.Console.WriteLine("i: " + i); } } 

3)当你使用LINQ Foreach时,你不能简单地将它分解为返回,因为它是一个委托函数。 所以我不得不实施keepGoing。

4)嵌套的委托函数不能返回值,所以我必须使用“ret”来设置LINQ Foreach中的返回值。

我希望这能回答你的问题,但是我不确定我是否理解。