используя yield в C #, как в Ruby - PullRequest
11 голосов
/ 03 июня 2010

Помимо использования 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 ]

19 голосов
/ 03 июня 2010

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

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

6 голосов
/ 04 июня 2010

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

Ключевое слово C # yield является , а не эквивалентом C # ключевого слова Ruby yield. На самом деле, не является эквивалентом ключевого слова 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 используется для выдачи control для аргумент блока

На самом деле, в 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: есть тонн вещей, которые можно сделать с блоком: преобразовать его, сохранить в атрибуте, передать это к другому методу, осмотрите это, напечатайте это & ​​hellip; Тем не менее, к far самое обычное, что нужно сделать - это позвонить. Так, matz добавил еще один специализированный синтаксис ярлыка для этого общего случая: yield означает «call блок, который был передан методу». Поэтому нам не нужно имя:

def if; yield end

Итак, что является 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); })
1 голос
/ 03 июня 2010

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

delegate void Action(MyThing myThing);
private static void forEachThing(Action action) 
{ 
    using (var connection = new Connection()) 
    { 
        action(new MyThing(connection));
    } 
}
0 голосов
/ 03 июня 2010

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

0 голосов
/ 03 июня 2010

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

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

...