Я думаю, что самая большая проблема заключается в том, что люди пытаются кодировать так, как если бы это был Java / C: они пытаются создавать чрезмерно общие приложения, которые никогда не нужно изменять при изменении будущих требований (что необходимо для Java / C, потому что эти приложения не не так легко изменить / изменить). В результате получается ужасно сложное приложение, которое негибко и невозможно поддерживать.
В Django это просто не нужно: просто пишите для современных требований, создавайте повторно используемые приложения с определенными, конкретными задачами и вносите изменения при необходимости. Все чаще и чаще я стараюсь писать вещи как можно проще, избегая чрезмерно сложных конструкций любой ценой.
Итераторы C # внутри являются конечными автоматами. Каждый раз, когда вы возвращаете что-то return
, место, где вы остановились, должно быть сохранено вместе с состоянием локальных переменных, чтобы вы могли вернуться и продолжить оттуда.
Чтобы удерживать это состояние, компилятор C # создает класс для хранения локальных переменных и место, откуда он должен продолжаться. Невозможно иметь значение ref
или out
в качестве поля в классе. Следовательно, если бы вам было разрешено объявить параметр как ref
или out
, не было бы возможности сохранить полный снимок функции на момент, когда мы остановились.
EDIT: Технически не все методы, возвращающие IEnumerable
, считаются итераторами. Итераторами считаются те, которые используют yield
для создания последовательности напрямую. Поэтому, хотя разделение итератора на два метода - хороший и распространенный обходной путь, он не противоречит тому, что я только что сказал. Внешний метод (который не использует напрямую yield
) не считается итератором.
На высоком уровне переменная ref может указывать на множество мест, включая типы значений, которые находятся в стеке. Время, когда итератор изначально создается путем вызова метода итератора, и когда назначается переменная ref, - это два очень разных момента. Невозможно гарантировать, что переменная, которая изначально была передана по ссылке, все еще существует, когда итератор действительно выполняется. Следовательно, это не разрешено (или поддается проверке)
Если вы хотите вернуть итератор и целое число из вашего метода, обходной путь:
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