在 C# 中使用 Yield,就像在 Ruby 中一样

发布于 2024-09-03 17:22:56 字数 927 浏览 6 评论 0原文

除了在 Ruby 中对迭代器使用 yield 之外,我还使用它在恢复被调用方法中的控制之前将控制权短暂地传递回调用者。我想在 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
}

...

这在 C# 中不起作用; ReSharper 告诉我 getThing 的主体不能是迭代器块,因为 MyThing 不是迭代器接口类型。这绝对是真的,但我不想遍历某些列表。我猜如果我不使用迭代器,我不应该使用 yield 。知道如何在 C# 中实现此块/闭包,这样我就不必将代码包装在 MyTest1MyTest2... 中,并将代码放在 < code>getThing() 的主体?

Besides just using yield for iterators in Ruby, I also use it to pass control briefly back to the caller before resuming control in the called method. What I want to do in C# is similar. In a test class, I want to get a connection instance, create another variable instance that uses that connection, then pass the variable to the calling method so it can be fiddled with. I then want control to return to the called method so that the connection can be disposed. I guess I'm wanting a block/closure like in Ruby. Here's the general idea:

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
}

...

This doesn't work in C#; ReSharper tells me that the body of getThing cannot be an iterator block because MyThing is not an iterator interface type. That's definitely true, but I don't want to iterate through some list. I'm guessing I shouldn't use yield if I'm not working with iterators. Any idea how I can achieve this block/closure thing in C# so I don't have to wrap my code in MyTest1, MyTest2, ... with the code in getThing()'s body?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

云醉月微眠 2024-09-10 17:22:56

您想要的是 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();
});

这捕获 t 的方式与 Ruby 中 yield 的方式相同。 C# yield 不同,它构造可以迭代的生成器。

What you want are lambda expressions, something like:

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

This captures t the same way yield does in Ruby. The C# yield is different, it constructs generators that can be iterated over.

你曾走过我的故事 2024-09-10 17:22:56

您说您想要使用 C# 的 yield 关键字,就像使用 Ruby 的 yield 关键字一样。您似乎对两者实际上所做的事情有点困惑:两者绝对没有任何关系,您所要求的根本不可能。

C# yield 关键字不是相当于 Ruby yield 关键字的 C# 关键字。事实上,没有相当于 C# 中的 Ruby yield 关键字。 Ruby 中相当于 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; }}

像这样使用它:

foreach (int i in Power(2, 8)) { Console.Write("{0} ", i); }

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

在 C# 中,yield 用于生成 到 < em>调用者,在 Ruby 中,yield 用于将控制交给块参数

事实上,在 Ruby 中,>yield 只是 Proc#call 的快捷方式。

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

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

class FalseClass
  def if(_); end
end

true.if(lambda { puts "It's true!" })

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

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

true.if(->{ puts "It's true!' })

但是,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!" }

现在,由于我们只能将一个块传递到方法中,因此我们实际上不需要显式命名该变量,因为无论如何都不会存在歧义:

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

但是,由于我们现在不再有可以发送的名称消息,我们需要其他方式。再次,我们得到了 Ruby 典型的 80/20 解决方案之一:人们可能想要对块做很多事情:转换它,将它存储在属性中,将它传递给另一个方法,检查它,打印它......但是,到目前为止,最常见的事情是调用它。因此,matz 针对这种常见情况添加了另一种专门的快捷语法:yield 表示“调用传递给方法的块”。因此,我们不需要名称:

def if; yield end

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

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

foo ->name { puts "Higher-order Hello World from #{name}!" }

C# 等效项完全相同:

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

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

You say you want to use C#'s yield keyword the same way you would use Ruby's yield keyword. You seem to be a little confused about what the two actually do: the two have absolutely nothing to do with each other, what you are asking for, is simply not possible.

The C# yield keyword is not the C# equivalent of the Ruby yield keyword. In fact, there is no equivalent to the Ruby yield keyword in C#. And the Ruby equivalent to C#'s yield keyword is not the yield keyword, it's the Enumerator::Yielder#yield method (also aliased as Enumerator::Yielder#<<).

IOW, it's for returning the next element of an iterator. Here's an abridged example from the official MSDN documentation:

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

Use it like so:

foreach (int i in Power(2, 8)) { Console.Write("{0} ", i); }

The Ruby equivalent would be something like:

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

In C#, yield is used to yield a value to the caller and in Ruby, yield is used to yield control to a block argument

In fact, in Ruby, yield is just a shortcut for Proc#call.

Imagine, if yield didn't exist. How would you write an if method in Ruby? It would look like this:

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

class FalseClass
  def if(_); end
end

true.if(lambda { puts "It's true!" })

This is kind of cumbersome. In Ruby 1.9, we get proc literals and a shortcut syntax for Proc#call, which make it a little bit nicer:

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

true.if(->{ puts "It's true!' })

However, Yukihiro Matsumoto noticed, that the vast majority of higher-order procedures only take one procedure argument. (Especially since Ruby has several control-flow constructs built into the language, which would otherwise require multiple procedure arguments, like if-then-else which would require two and case-when which would require n arguments.) So, he created a specialized way to pass exactly one procedural argument: the block. (In fact, we already saw an example of this at the very beginning, because Kernel#lambda is actually just a normal method which takes a block and returns a Proc.)

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

true.if { puts "It's true!" }

Now, since we can only ever pass exactly one block into a method, we really don't need to explicitly name the variable, since there can never be an ambiguity anyway:

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

However, since we now no longer have a name that we can send messages to, we need some other way. And again, we get one of those 80/20 solutions that are so typical for Ruby: there are tons of things that one might want to do with a block: transform it, store it in an attribute, pass it to another method, inspect it, print it … However, by far the most common thing to do is to call it. So, matz added another specialized shortcut syntax for exactly this common case: yield means "call the block that was passed to the method". Therefore, we don't need a name:

def if; yield end

So, what is the C# equivalent to Ruby's yield keyword? Well, let's go back to the first Ruby example, where we explicitly passed the procedure as an argument:

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

foo ->name { puts "Higher-order Hello World from #{name}!" }

The C# equivalent is exactly the same:

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

Foo(name => { Console.WriteLine("Higher-order Hello World from {0]!", name); })
羞稚 2024-09-10 17:22:56

我可能会将委托传递给迭代器。

delegate void Action(MyThing myThing);
private static void forEachThing(Action action) 
{ 
    using (var connection = new Connection()) 
    { 
        action(new MyThing(connection));
    } 
}

I might pass a delegate into the iterator.

delegate void Action(MyThing myThing);
private static void forEachThing(Action action) 
{ 
    using (var connection = new Connection()) 
    { 
        action(new MyThing(connection));
    } 
}
我的奇迹 2024-09-10 17:22:56

C# 中的 yield 专门用于返回迭代集合的位。具体来说,您的函数必须返回 IEnumerableIEnumerable 才能使 yield 正常工作,并且它应该在 内部使用。代码>foreach循环。它是 C# 中非常具体的构造,不能按照您尝试的方式使用。

我不确定是否还有其他可以使用的构造,可能是带有 lambda 表达式的构造。

yield in C# is specifically for returning bits of an iterated collection. Specifically, your function has to return IEnumerable<Thing> or IEnumerable for yield to work, and it's meant to be used from inside of a foreach loop. It is a very specific construct in c#, and it can't be used in the way you're trying.

I'm not sure off the top of my head if there's another construct that you could use or not, possibly something with lambda expressions.

怀中猫帐中妖 2024-09-10 17:22:56

您可以让 GetThing 获取包含要执行的代码的委托,然后从其他函数传递匿名方法。

You can have GetThing take a delegate containing the code to execute, then pass anonymous methods from other functions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文