Ключевое слово Yield, дающее неожиданные значения для IEnumerable - PullRequest
0 голосов
/ 08 февраля 2020

Это мой код (метод расширения)

public static IEnumerable<uint> GetFieldVals(this DataSource rs, IEnumerable<string> columnNames, Predicate<uint> shouldRun)
        {
            var rList = new List<uint>();
            if (columnNames.Any())

                foreach (var name in columnNames)
                {
                    rs.GetFieldVal(name, out uint temp);
                    if (shouldRun(temp))
                    {
                        rList.Add(temp);
                    }

                }
            return rList;
        }

Это работает. Однако, если я изменю это на это, все результаты будут последним элементом в сгенерированной коллекции (хотя Count является правильным значением).

public static IEnumerable<uint> GetFieldVals(this DataSource rs, IEnumerable<string> columnNames, Predicate<uint> shouldRun)
{

    if (!columnNames.Any()) yield break;

    foreach (var name in columnNames)
    {
        rs.GetFieldVal(name, out uint temp);
        if (shouldRun(temp))
        {
            yield return temp;
        }

    }
}

Что дает?

РЕДАКТИРОВАТЬ Спасибо всем за ваши комментарии. Я написал это в какой-то спешке, а потом провел напряженные выходные, поэтому я не смог правильно решить эту проблему. Я сделаю это сейчас. Вы все на 100% правы, что я упустил слишком много.

Я пытаюсь взять неуклюжий API DataSource и создать с его помощью IEnumerable элементов valueobject (с которым проще и гибче работать). Я реализую это с помощью фабрики, чтобы сохранить ее переносимость; моя реализация метода фабрики вызывает код, который я написал в моем оригинальном посте. Вот пример того, как выглядит мой valueobject:

public class MyTableDataObject : IDataObject<uint>
{
    public uint ID { get; set; }
    public string Name { get; set; }

    //MOAR properties

    public IEnumerable<uint> SomeCollection { get; set; }

    //MOAR properties

}

Проблема, о которой я говорил, возникает, когда у меня есть коллекция некоторого типа в качестве свойства в моем valueobject (ie «SomeCollection» во фрагменте выше)

FWIW, вот мой код для коллекции имен столбцов, которую я передаю методу расширения из моего исходного сообщения.

    public static IEnumerable<string> ColumnNames
    {
        get
        {

            yield return "COLUMNNAME00";
            yield return "COLUMNNAME01";
            yield return "COLUMNNAME02";
            yield return "COLUMNNAME03";
            yield return "COLUMNNAME04";
            yield return "COLUMNNAME05";
            yield return "COLUMNNAME06";
            yield return "COLUMNNAME07";
            yield return "COLUMNNAME08";
            yield return "COLUMNNAME09";
            yield return "COLUMNNAME10";
            yield return "COLUMNNAME11";
            yield return "COLUMNNAME12";
            yield return "COLUMNNAME13";
            yield return "COLUMNNAME14";
            yield return "COLUMNNAME15";
        }
    }

Вот код вызова.

var rs = new DataSource();
rs.Open("Select * From MyTable");

//The Generic type on the enumerable indicates the type of the identifier of the items, not that the Enumerable is itself a list of uints. Do not get confused by this!
var dse = new DataSourceEnumerable<uint>(rs, new MyTableDataObjectFactory());

using (var writer = new MyWriterFacade("MyOutput.json"))
{
    var json = new JsonSerializer(); //Newtonsoft.Json lib
    var str = JsonConvert.SerializeObject(dse, Formatting.Indented);
    writer.Write(str);   

}

Несмотря на то, что значения выходного файла json в основном правильные, каждый «SomeCollection» содержит одинаковые элементы (я думаю, что это значения SomeCollection последнего элемента), когда я использую ключевое слово yield. Когда я не использую yield и использую более традиционный код, вывод json иллюстрирует правильные значения для каждого SomeCollection в файле.

Это код в фактическом перечисляемом:

public DataSourceEnumerable(DataSource ds, DataObjectFactory<T, DataSource> factory)
{
    ds.MoveFirst();
    innerList = new List<IDataObject<T>>();

    _enumerator = Create(ds, factory, innerList);
}

public static IEnumerator<IDataObject<T>> Create(DataSource ds, DataObjectFactory<T, DataSource> factory,
    IList<IDataObject<T>> innerList)
{   
    while (!ds.Eof)
    {
        innerList.Add(factory.InitializeDataObject<object, object>(ds));
        ds.MoveNext();
    }
    return new DataSourceEnumerator(innerList);
}

Я надеюсь, что это проливает некоторый свет на это, если кто-нибудь может сломать это для меня немного лучше. Цени это!

1 Ответ

4 голосов
/ 08 февраля 2020

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

Теперь: иногда может происходить перемена между вызовом метода, который возвращает последовательность и выполняет итерацию этой последовательности. Например, содержимое источника данных или параметров последовательности полей может измениться. Или логика c предиката может измениться, обычно из-за «захваченных переменных». Итак: разница в коде, который называет это , который мы не видим. Но: ищите сроки между вызовом метода и его итерацией (foreach et c).

...