Разделение отложенного IEnumerable на две последовательности без повторной оценки?

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

class Pets
{
    public IEnumerable<Cat> Cats { get; set; }
    public IEnumerable<Dog> Dogs { get; set; }
}

Pets GetPets(IEnumerable<PetRequest> requests) { ... }

Базовая модель полностью способна обрабатывать всю последовательность элементов PetRequest одновременно, а также PetRequest в основном является общей информацией, такой как идентификатор, поэтому он делает нет смысла пытаться разбивать запросы на входе. Но на самом деле провайдер не возвращает экземпляры Cat и Dog , а только общую структуру данных:

class PetProvider
{
    IEnumerable<PetData> GetPets(IEnumerable<PetRequest> requests)
    {
        return HandleAllRequests(requests);
    }
}

Вместо этого я назвал тип ответа PetData of Pet , чтобы четко указать, что это не суперкласс Cat или Dog - другими словами, преобразование в Cat или Dog - это процесс отображения. Также следует иметь в виду, что HandleAllRequests стоит дорого, например запрос к базе данных, поэтому я действительно не хочу его повторять, и я бы предпочел избегать кеширования результатов в памяти с помощью ToArray () или тому подобного, потому что может быть тысячи или миллионы результатов (у меня много домашних животных).

Пока что мне удалось собрать воедино этот неуклюжий прием:

Pets GetPets(IEnumerable<PetRequest> requests)
{
    var data = petProvider.GetPets(requests);
    var dataGroups = 
        from d in data
        group d by d.Sound into g
        select new { Sound = g.Key, PetData = g };
    IEnumerable<Cat> cats = null;
    IEnumerable<Dog> dogs = null;
    foreach (var g in dataGroups)
        if (g.Sound == "Bark")
            dogs = g.PetData.Select(d => ConvertDog(d));
        else if (g.Sound == "Meow")
            cats = g.PetData.Select(d => ConvertCat(d));
    return new Pets { Cats = cats, Dogs = dogs };
}

Технически это работает, в том смысле, что это не так. не приводит к двойному перечислению результатов PetData , но имеет две основные проблемы:

  1. Это похоже на гигантскую прыщику в коде; это попахивает ужасным императивным стилем, который мы всегда использовали в pre-LINQ framework 2.0.

  2. Это заканчивается совершенно бессмысленным упражнением, потому что метод GroupBy просто кэширует все эти результаты в памяти, а это значит, что мне действительно не лучше, чем если бы я просто поленился и сделал ToList () в первую очередь и добавил несколько предикатов.

Итак, чтобы повторить вопрос :

Можно ли разделить один отложенный экземпляр IEnumerable на два экземпляра IEnumerable , без выполнения каких-либо активных оценок, кэширования приводит к появлению памяти или необходимости повторно оценивать исходный IEnumerable во второй раз?

По сути, это было бы обратной операцией Concat . Тот факт, что его еще нет в .NET framework, является убедительным свидетельством того, что это может быть даже невозможно, но я подумал, что в любом случае не повредит спросить.

P.S. Пожалуйста, не говорите мне создать суперкласс Pet и просто вернуть IEnumerable . Я использовал Cat и Dog в качестве забавных примеров, но на самом деле типы результатов больше похожи на Item и Error - они оба являются производными из одних и тех же общих данных, но в остальном не имеют ничего общего.

9
задан Aaronaught 14 June 2011 в 16:37
поделиться