Разделение отложенного IEnumerable <T>на две последовательности без переоценки? - PullRequest
9 голосов
/ 14 июня 2011

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

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 вместо 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<T> экземпляр на два IEnumerable<?> экземпляра, без выполнения каких-либо активных оценок, кэшированиеприводит к памяти или необходимости переоценивать оригинал IEnumerable<T> во второй раз?

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

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

Ответы [ 2 ]

12 голосов
/ 14 июня 2011

Принципиально, нет.Представьте, если бы было возможным.Затем подумайте, что произойдет, если я сделаю:

foreach (Cat cat in pets.Cats)
{
    ...
}

foreach (Dog dog in pets.Dogs)
{
    ...
}

Это должно сначала обработать всех кошек, а затем всех собак ... так что может произойти с исходной последовательностью, если первый элемент - Dog?Он либо должен его кэшировать или пропустить - он не может его вернуть, потому что мы все еще просим Cats.

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

Если это вообще возможноВы действительно просто хотите обращаться с домашними животными (будь то кошки или собаки), когда вы их забираете.Будет ли возможным предоставить Action<Cat> и Action<Pet> и выполнить правильный обработчик для каждого элемента?

2 голосов
/ 14 июня 2011

Что Джон сказал (я уверен, что я один миллионный человек, который скажет это).

Я бы, наверное, просто пошел в старую школу и сделал бы:

List<Cat> cats = new List<Cat>();
List<Dog> dog = new List<Dog>();

foreach(var pet in data)
{
   if (g.Sound == "Bark")
     dogs.Add(ConvertDog(pet));
   else if (pet.Sound == "Meow")
     cats.Add(ConvertCat(pet));
}

Но я понимаю, что это не совсем то, что вы хотите сделать - но тогда вы сказали re -оценку - и это действительно только один раз:)

...