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.
精彩评论