Используя выход в С#, как я бы в 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 взять делегат, содержащий исполняемый код, затем передать анонимные методы из других функций.