Почему методы итератора не могут взять или 'касательно' или параметры?

Я думаю, что самая большая проблема заключается в том, что люди пытаются кодировать так, как если бы это был Java / C: они пытаются создавать чрезмерно общие приложения, которые никогда не нужно изменять при изменении будущих требований (что необходимо для Java / C, потому что эти приложения не не так легко изменить / изменить). В результате получается ужасно сложное приложение, которое негибко и невозможно поддерживать.

В Django это просто не нужно: просто пишите для современных требований, создавайте повторно используемые приложения с определенными, конкретными задачами и вносите изменения при необходимости. Все чаще и чаще я стараюсь писать вещи как можно проще, избегая чрезмерно сложных конструкций любой ценой.

37
задан hello_there_andy 8 May 2015 в 22:03
поделиться

3 ответа

Итераторы C # внутри являются конечными автоматами. Каждый раз, когда вы возвращаете что-то return , место, где вы остановились, должно быть сохранено вместе с состоянием локальных переменных, чтобы вы могли вернуться и продолжить оттуда.

Чтобы удерживать это состояние, компилятор C # создает класс для хранения локальных переменных и место, откуда он должен продолжаться. Невозможно иметь значение ref или out в качестве поля в классе. Следовательно, если бы вам было разрешено объявить параметр как ref или out , не было бы возможности сохранить полный снимок функции на момент, когда мы остановились.

EDIT: Технически не все методы, возвращающие IEnumerable , считаются итераторами. Итераторами считаются те, которые используют yield для создания последовательности напрямую. Поэтому, хотя разделение итератора на два метода - хороший и распространенный обходной путь, он не противоречит тому, что я только что сказал. Внешний метод (который не использует напрямую yield ) не считается итератором.

48
ответ дан 27 November 2019 в 04:33
поделиться

На высоком уровне переменная ref может указывать на множество мест, включая типы значений, которые находятся в стеке. Время, когда итератор изначально создается путем вызова метода итератора, и когда назначается переменная ref, - это два очень разных момента. Невозможно гарантировать, что переменная, которая изначально была передана по ссылке, все еще существует, когда итератор действительно выполняется. Следовательно, это не разрешено (или поддается проверке)

5
ответ дан 27 November 2019 в 04:33
поделиться

Если вы хотите вернуть итератор и целое число из вашего метода, обходной путь:

public class Bar : IFoo
{
    public IEnumerable<int> GetItems( ref int somethingElse )
    {
        somethingElse = 42;
        return GetItemsCore();
    }

    private IEnumerable<int> GetItemsCore();
    {
        yield return 7;
    }
}

Следует отметить, что ни один из кодов внутри метода итератора (т.е. в основном метод который содержит yield return или yield break ), выполняется до тех пор, пока не будет вызван метод MoveNext () в перечислителе. Итак, если бы вы могли использовать out или ref в своем методе итератора, вы бы получили такое удивительное поведение, как это:

// This will not compile:
public IEnumerable<int> GetItems( ref int somethingElse )
{
    somethingElse = 42;
    yield return 7;
}

// ...
int somethingElse = 0;
IEnumerable<int> items = GetItems( ref somethingElse );
// at this point somethingElse would still be 0
items.GetEnumerator().MoveNext();
// but now the assignment would be executed and somethingElse would be 42

Это распространенная ошибка, связанная с этим проблема:

public IEnumerable<int> GetItems( object mayNotBeNull ){
  if( mayNotBeNull == null )
    throw new NullPointerException();
  yield return 7;
}

// ...
IEnumerable<int> items = GetItems( null ); // <- This does not throw
items.GetEnumerators().MoveNext();                    // <- But this does

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

public IEnumerable<int> GetItems( object mayNotBeNull ){
  if( mayNotBeNull == null )
    throw new NullPointerException();
  // other quick checks
  return GetItemsCore( mayNotBeNull );
}

private IEnumerable<int> GetItemsCore( object mayNotBeNull ){
  SlowRunningMethod();
  CallToDatabase();
  // etc
  yield return 7;
}    
// ...
IEnumerable<int> items = GetItems( null ); // <- Now this will throw

РЕДАКТИРОВАТЬ: Если вы действительно хотите, чтобы при перемещении итератора параметр ref изменялся, вы можете сделать что-то вроде этого:

public static IEnumerable<int> GetItems( Action<int> setter, Func<int> getter )
{
    setter(42);
    yield return 7;
}

//...

int local = 0;
IEnumerable<int> items = GetItems((x)=>{local = x;}, ()=>local);
Console.WriteLine(local); // 0
items.GetEnumerator().MoveNext();
Console.WriteLine(local); // 42
17
ответ дан 27 November 2019 в 04:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: