использование yield в C#, как и в Ruby

кроме того, только с помощью 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()тело?

5 ответов


то, что вы хотите, это лямбда-выражения, что-то вроде:

// 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 в Руби. C# yield отличается, он создает генераторы,которые можно перебирать.


вы говорите, что хотите использовать C# ' S yield ключевое слово так же, как вы бы использовать Руби yield ключевое слово. Вы, кажется, немного смущены тем, что они на самом деле делают: у них есть абсолютно ничего

В C# yield ключевое слово не C# эквивалент Рубина yield ключевое слово. На самом деле, там нет эквивалент Рубина yield ключевое слово в С.# И Рубин, эквивалентный C# ' S yield ключевое слово не the 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 и ярлык синтаксис Proc#call, что делает его немного приятнее:

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

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

однако, Юкихиро Мацумото заметил, что тот vast большинство процедур более высокого порядка принимают только один процедура аргумент. (Тем более, что 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: there are Т вещей, которые можно было бы сделать с блоком: преобразовать его, сохранить его в атрибуте, передать его другому методу, проверить его, распечатать его ... однако далеко самое распространенное, что нужно сделать, это назвать его. Таким образом, matz добавил еще один специализированный синтаксис ярлыка именно для этого общего случая:yield означает "call блок, который был передан в метод". Поэтому нам не нужно имя:

def if; yield end

так что is эквивалент C# для Ruby's 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); })


Я мог бы передать делегат в итератор.

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

yield в C# специально для возврата битов итерационной коллекции. В частности, ваша функция должна возвращать IEnumerable<Thing> или IEnumerable на yield для работы, и он предназначен для использования изнутри foreach петли. Это очень специфическая конструкция в C#, и ее нельзя использовать так, как вы пытаетесь.

Я не уверен, что есть еще одна конструкция, которую вы могли бы использовать или нет, возможно, что-то с лямбда-выражениями.


вы можете GetThing возьмите делегат, содержащий код для выполнения, а затем передайте анонимные методы из других функций.