在C#中使用yield就像在Ruby中一样

Sar*_*els 12 c# closures yield dry

除了yield在Ruby中使用迭代器之外,我还使用它将控制权简单地传递回调用者,然后在被调用的方法中恢复控制.我想在C#中做的事情是类似的.在测试类中,我想获取一个连接实例,创建另一个使用该连接的变量实例,然后将该变量传递给调用方法,以便可以进行调整.然后,我希望控制返回到被调用的方法,以便可以处理连接.我想我想要像Ruby一样的块/闭包.这是一般的想法:

private static MyThing getThing()
{
    using (var connection = new Connection())
    {
        yield return new MyThing(connection);
    }
}

[TestMethod]
public void MyTest1()
{
    // call getThing(), use yielded MyThing, control returns to getThing()
    // for disposal
}

[TestMethod]
public void MyTest2()
{
    // call getThing(), use yielded MyThing, control returns to getThing()
    // for disposal
}

...
Run Code Online (Sandbox Code Playgroud)

这在C#中不起作用; ReSharper告诉我,body getThing不能是迭代器块,因为MyThing它不是迭代器接口类型.这绝对是正确的,但我不想迭代一些列表.我猜我不应该使用,yield如果我不使用迭代器.任何想法,我怎样才能实现在C#此块/关闭的事情,所以我没有给缠上了我的代码MyTest1,MyTest2......与代码getThing()的身上?

Bli*_*ndy 19

你想要的是lambda表达式,如:

// not named GetThing because it doesn't return anything
private static void Thing(Action<MyThing> thing)
{
    using (var connection = new Connection())
    {
        thing(new MyThing(connection));
    }
}

// ...
// you call it like this
Thing(t=>{
  t.Read();
  t.Sing();
  t.Laugh();
});
Run Code Online (Sandbox Code Playgroud)

tyieldRuby中的方式相同.C#yield是不同的,它构造了可以迭代的生成器.


Jör*_*tag 7

你说你想使用C#的yield关键字就像使用Ruby的yield关键字一样.你似乎对这两者实际上做了什么有点困惑:两者完全无关,你要求的是,根本不可能.

C#yield关键字不是 Ruby yield关键字的C#等价物.实际上,C#中没有与Ruby yield关键字相同的东西.和红宝石等同于C#的yield关键字是没有yield 关键字,它的Enumerator::Yielder#yield 方法(也别名为Enumerator::Yielder#<<).

IOW,它用于返回迭代器的下一个元素.以下是官方MSDN文档中的简略示例:

public static IEnumerable Power(int number, int exponent) {
    var counter = 0;
    var result = 1;
    while (counter++ < exponent) {
        result *= number;
        yield return result; }}
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

foreach (int i in Power(2, 8)) { Console.Write("{0} ", i); }
Run Code Online (Sandbox Code Playgroud)

Ruby等价物将是这样的:

def power(number, exponent)
  Enumerator.new do |yielder|
    result = 1
    1.upto(exponent-1) { yielder.yield result *= number } end end

puts power(2, 8).to_a
Run Code Online (Sandbox Code Playgroud)

在C#,yield用来产生一个呼叫者和Ruby中,yield用于产生控制到一个块参数

事实上,在Ruby中,yield只是一个捷径Proc#call.

想象一下,如果yield不存在的话.你会如何if在Ruby中编写方法?它看起来像这样:

class TrueClass
  def if(code)
    code.call
  end
end

class FalseClass
  def if(_); end
end

true.if(lambda { puts "It's true!" })
Run Code Online (Sandbox Code Playgroud)

这有点麻烦.在Ruby 1.9中,我们获得了proc文字和快捷语法Proc#call,这使它更好一些:

class TrueClass
  def if(code)
    code.()
  end
end

true.if(->{ puts "It's true!' })
Run Code Online (Sandbox Code Playgroud)

然而,Yukihiro Matsumoto注意到,绝大多数高阶程序只采用一个程序参数.(特别是因为Ruby在语言中内置了几个控制流构造,否则需要多个过程参数,比如if-then-else需要两个参数,case-when这需要n个参数.)因此,他创建了一种专门的方法来传递一个过程参数:块.(事实上​​,我们在一开始就已经看到了这个例子,因为Kernel#lambda它实际上只是一个普通的方法,它接受一个块并返回一个Proc.)

class TrueClass
  def if(&code)
    code.()
  end
end

true.if { puts "It's true!" }
Run Code Online (Sandbox Code Playgroud)

现在,因为我们只能将一个块传递给一个方法,所以我们真的不需要显式地命名变量,因为无论如何都不会有歧义:

def if
  ???.() # But what do we put here? We don't have a name to call #call on!
end
Run Code Online (Sandbox Code Playgroud)

但是,由于我们现在不再拥有可以发送消息的名称,我们还需要其他方式.再次,我们得到那些80/20解决方案是如此典型的红宝石之一:有人们可能希望与块做的事情:改造它,它保存在一个属性,它传递给另一种方法,检查它,打印它...然而,到目前为止,最常见的事情是调用它.因此,matz为这种常见情况添加了另一种专用的快捷语法:yield意思是" call传递给方法的块".因此,我们不需要名称:

def if; yield end
Run Code Online (Sandbox Code Playgroud)

那么,什么 C#等同于Ruby的yield关键字?好吧,让我们回到第一个Ruby示例,我们显式地将该过程作为参数传递:

def foo(bar)
  bar.('StackOverflow')
end

foo ->name { puts "Higher-order Hello World from #{name}!" }
Run Code Online (Sandbox Code Playgroud)

C#等价物完全相同:

void Foo(Action<string> bar) => bar("StackOverflow")

Foo(name => { Console.WriteLine("Higher-order Hello World from {0]!", name); })
Run Code Online (Sandbox Code Playgroud)