Используя выход в С#, как я бы в Ruby
Помимо использования yield
для итераторов в Ruby, я также использую его для кратковременного возврата элемента управления к вызывающему, прежде чем возобновить управление в вызываемом методе. То, что я хочу делать на С#, похоже. В тестовом классе я хочу получить экземпляр соединения, создать другой экземпляр переменной, который использует это соединение, а затем передать переменную вызывающему методу, чтобы его можно было воспроизвести. Затем я хочу, чтобы управление возвращалось к вызываемому методу, чтобы соединение могло быть удалено. Наверное, мне нужен блок/закрытие, как в 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
}
...
Это не работает в С#; ReSharper сообщает мне, что тело getThing
не может быть блоком итератора, потому что MyThing
не является типом интерфейса итератора. Это определенно верно, но я не хочу перебирать некоторые списки. Я предполагаю, что я не должен использовать yield
, если я не работаю с итераторами. Любая идея, как я могу достичь этой операции блокировки/закрытия в С#, поэтому мне не нужно переносить код в MyTest1
, MyTest2
,... с кодом в getThing()
body?
Ответы
Ответ 1
Что вы хотите - это лямбда-выражения, например:
// 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. С# yield
отличается, он строит генераторы, которые могут быть повторены.
Ответ 2
Вы говорите, что хотите использовать ключевое слово С# yield
так же, как использовать ключевое слово Ruby yield
. Кажется, вы немного смущены тем, что на самом деле делают два: они не имеют абсолютно никакого отношения друг к другу, то, что вы просите, просто невозможно.
Ключевое слово С# yield
не является эквивалентом С# ключевого слова Ruby yield
. На самом деле, нет эквивалента ключевому слову Ruby yield
в С#. И эквивалент Ruby для ключевого слова С# 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
В С#, 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 true!" })
Это довольно громоздко. В Ruby 1.9 мы получаем proc литералы и синтаксис ярлыков для Proc#call
, что делает его немного приятнее:
class TrueClass
def if(code)
code.()
end
end
true.if(->{ puts "It true!' })
Однако Юкихиро Мацумото заметил, что подавляющее большинство процедур более высокого порядка принимает только один аргумент процедуры. (Тем более, что Ruby имеет несколько встроенных в язык построений с контролем потока, которые в противном случае требовали бы нескольких аргументов процедуры, таких как if-then-else
, для которых требовалось бы два и case-when
, для которых потребовались бы n аргументов.) Таким образом, он создал специализированный способ передать ровно один процедурный аргумент: блок. (На самом деле мы уже видели пример этого в самом начале, потому что Kernel#lambda
на самом деле является просто нормальным методом, который берет блок и возвращает a Proc
.)
class TrueClass
def if(&code)
code.()
end
end
true.if { puts "It true!" }
Теперь, поскольку мы можем передавать только один блок только в один метод, нам действительно не нужно явно указывать переменную, так как в любом случае не может быть двусмысленности:
def if
???.() # But what do we put here? We don't have a name to call #call on!
end
Однако, поскольку теперь у нас больше нет имени, на которое мы можем отправлять сообщения, нам нужен другой способ. И снова мы получаем один из тех 80/20 решений, которые так типичны для Ruby: есть много вещей, которые можно было бы сделать с блоком: преобразовать его, сохранить в атрибуте, передать его другому методу, проверить это, напечатать его & hellip; Тем не менее, наиболее распространенной задачей является называть это. Итак, matz добавил еще один специализированный синтаксис ярлыков именно для этого обычного случая: yield
означает "call
блок, который был передан методу". Поэтому нам не нужно имя:
def if; yield end
Итак, что эквивалентно С# для ключевого слова Ruby yield
? Итак, вернемся к первому примеру Ruby, где мы явно передали процедуру как аргумент:
def foo(bar)
bar.('StackOverflow')
end
foo ->name { puts "Higher-order Hello World from #{name}!" }
эквивалент С# точно такой же:
void Foo(Action<string> bar) => bar("StackOverflow")
Foo(name => { Console.WriteLine("Higher-order Hello World from {0]!", name); })
Ответ 3
Я могу передать делегат в итератор.
delegate void Action(MyThing myThing);
private static void forEachThing(Action action)
{
using (var connection = new Connection())
{
action(new MyThing(connection));
}
}
Ответ 4
yield
в С# специально для возвращаемых бит итерированной коллекции. В частности, ваша функция должна возвращать IEnumerable<Thing>
или IEnumerable
для yield
для работы, и она должна использоваться изнутри цикла foreach
. Это очень специфическая конструкция в С#, и она не может использоваться так, как вы пытаетесь.
Я не уверен в верхней части головы, если есть другая конструкция, которую вы могли бы использовать или нет, возможно, что-то с лямбда-выражениями.
Ответ 5
У вас может быть GetThing
взять делегат, содержащий исполняемый код, затем передать анонимные методы из других функций.