开发者

using yield in C# like I would in Ruby

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?


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.


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


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


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜