Помимо просто использования 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
}
...
Это не работает в C#; ReSharper говорит мне что тело getThing
не может быть блок итератора потому что MyThing
не тип интерфейса итератора. Это определенно верно, но я не хочу выполнять итерации через некоторый список. Я предполагаю, что не должен использовать yield
если я не работаю с итераторами. Любая идея, как я могу достигнуть этой вещи блока/закрытия в C#, таким образом, я не должен переносить свой код в MyTest1
, MyTest2
... с кодом в getThing()
тело?
Что вам нужно, так это лямбда-выражения, что-то вроде:
// 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
так же, как yield
в Ruby. В C# yield
отличается, он строит генераторы, которые можно итерировать.
Я мог бы передать делегата итератору.
delegate void Action(MyThing myThing);
private static void forEachThing(Action action)
{
using (var connection = new Connection())
{
action(new MyThing(connection));
}
}
Можно поставить GetThing
взять делегат, содержащий код, для выполнения, а затем передать анонимные методы из других функций.
yield
в C# предназначен специально для возврата битов итерируемой коллекции. В частности, ваша функция должна возвращать IEnumerable
или IEnumerable
, чтобы yield
работал, и он предназначен для использования внутри цикла foreach
. Это очень специфическая конструкция в c#, и она не может быть использована так, как вы пытаетесь.
Я не уверен, есть ли другая конструкция, которую вы можете использовать или нет, возможно, что-то с лямбда-выражениями.
Вы говорите, что хотите использовать ключевое слово C # yield
так же, как вы использовали бы ключевое слово Ruby yield
. Вы, кажется, немного запутались в том, что эти двое на самом деле делают: у них нет абсолютно ничего общего друг с другом, то, о чем вы просите, просто невозможно.
Ключевое слово C # yield
- это , а не эквивалент C # ключевого слова yield в Ruby . Фактически, не существует эквивалента ключевого слова Ruby
yield
в C #. И Ruby-эквивалент ключевого слова yield
в C # - это , а не ключевое слово 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
используется для получения значение для вызывающего , а в 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!" })
Это довольно громоздко. В Ruby 1.9 мы получили литералы процедур и сокращенный синтаксис для Proc # call
, что делает его немного лучше:
class TrueClass
def if(code)
code.()
end
end
true.if(->{ puts "It's true!' })
Однако Юкихиро Мацумото заметил, что подавляющее большинство процедуры высшего порядка принимают только один аргумент процедуры. (Тем более, что 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
Однако, поскольку мы больше не есть имя, на которое мы можем отправлять сообщения, нам нужен другой способ. И снова мы получаем одно из тех решений 80/20, которые так типичны для Ruby: существует тонн вещей, которые можно было бы сделать с блоком: преобразовать его, сохранить в атрибуте, передать его в другой метод, изучите, распечатайте… Однако, по далеко , наиболее распространенным является его вызов. Итак, matz добавил еще один специальный сокращенный синтаксис именно для этого распространенного случая: yield
означает « call
блок, который был передан методу». Следовательно, нам не нужно имя:
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); })