Глубокой вложенности, когда обходишь объектную модель идет от 3-й части


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

Код в таком виде (с переменной и имена типа изменилось). Как я могу очистить этот беспорядок, когда я не могу изменить структуру rootObject?

(Это .Net 3.5 с.)

var levelOneEnumerator = rootObject.GetEnumerator();
while (levelOneEnumerator.MoveNext())
{
    var levelOneItem = levelOneEnumerator.Current as Foo_LevelOneItem;
    if (levelOneItem == null) continue;

    var levelTwoItemsEnumerator = levelOneItem.LevelTwoItems.GetEnumerator();
    while (levelTwoItemsEnumerator.MoveNext())
    {
        var LevelTwoItemsItem = levelTwoItemsEnumerator.Current as Foo_LevelTwoItem;
        if (LevelTwoItemsItem == null) continue;

        var foobars = new List<FooBar>();

        var levelThreeItemsEnumerator = LevelTwoItemsItem.LevelThreeItems.GetEnumerator();
        while (levelThreeItemsEnumerator.MoveNext())
        {
            var levelThreeItem = levelThreeItemsEnumerator.Current as Foo_LevelThreeItem;
            if (levelThreeItem == null) continue;

            var levelFourItemsEnumerator = levelThreeItem.LevelFourItems.GetEnumerator();
            while (levelFourItemsEnumerator.MoveNext())
            {
                var levelFourItem = levelFourItemsEnumerator.Current as Foo_LevelFourItem;
                if (levelFourItem == null) continue;

                var levelFiveItemsEnumerator = levelFourItem.LevelFiveItems.GetEnumerator();
                while (levelFiveItemsEnumerator.MoveNext())
                {
                    var levelFiveItem = levelFiveItemsEnumerator.Current as Foo_LevelFiveItem;
                    if (levelFiveItem == null) continue;

                    var levelSixItemsEnumerator = levelFiveItem.LevelSixItems.GetEnumerator();
                    while (levelSixItemsEnumerator.MoveNext())
                    {
                        var levelSixItem = levelSixItemsEnumerator.Current as Foo_LevelSixItem;
                        if (levelSixItem == null) continue;

                        var levelSixKey = levelSixItem.Key;
                        var foobar = foobars.Where(x => x.Key == levelSixKey).FirstOrDefault();
                        if (foobar == null)
                        {
                            foobar = new FooBar
                                          {
                                              LevelSixKey = levelSixKey,
                                              TransDate = levelFiveItem.TransDate,
                                              PaidAmount = 0
                                          };
                            foobars.Add(foobar);
                        }

                        // * -1 because value should be positive, while product reports a negative (and vice versa)
                        foobar.PaidAmount += (levelFiveItem.PaidAmount ?? 0) * -1; 
                    }
                }
            }
        }

        yield return new FooBarsCollection
                         {
                             Prop1 = levelTwoItemsItem.Prop1,
                             Prop2 = levelTwoItemsItem.Prop2,
                             Prop3 = levelTwoItemsItem.Prop3,
                             FooBars = foobars
                         };
    }
}


5982
11
задан 22 февраля 2011 в 09:02 Источник Поделиться
Комментарии
4 ответа

Прежде всего, я хотел понять, что

var levelOneEnumerator = rootObject.GetEnumerator();
while (levelOneEnumerator.MoveNext())
{
var levelOneItem = levelOneEnumerator.Current as Foo_LevelOneItem;
if (levelOneItem == null) continue;

эквивалентно намного короче

foreach (var levelOneItem in rootObject.OfType<Foo_LevelOneItem>())
{

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

foreach (var levelOneItem in rootObject.OfType<Foo_LevelOneItem>())
foreach (var levelTwoItem in levelOneItem.LevelTwoItems.OfType<Foo_LevelTwoItem>())
yield return new FooBarsCollection
{
Prop1 = levelTwoItem.Prop1,
Prop2 = levelTwoItem.Prop2,
Prop3 = levelTwoItem.Prop3,
FooBars = getFoobars(levelTwoItem)
};

[...]

private static List<FooBar> getFoobars(Foo_LevelTwoItem levelTwoItem)
{
var foobars = new List<FooBar>();

foreach (var levelThreeItem in levelTwoItem.LevelThreeItems.OfType<Foo_LevelThreeItem>())
foreach (var levelFourItem in levelThreeItem.LevelFourItems.OfType<Foo_LevelFourItem>())
foreach (var levelFiveItem in levelFourItem.LevelFiveItems.OfType<Foo_LevelFiveItem>())
foreach (var levelSixItem in levelFiveItem.LevelSixItems.OfType<Foo_LevelSixItem>())
processLevelSixItem(foobars, levelFiveItem, levelSixItem.Key);

return foobars;
}

private static void processLevelSixItem(List<FooBar> foobars, Foo_LevelFiveItem levelFiveItem, Foo_LevelSixItemKey levelSixKey)
{
var foobar = foobars.Where(x => x.Key == levelSixKey).FirstOrDefault();
if (foobar == null)
{
foobar = new FooBar
{
LevelSixKey = levelSixKey,
TransDate = levelFiveItem.TransDate,
PaidAmount = 0
};
foobars.Add(foobar);
}

// * -1 because value should be positive, while product reports a negative (and vice versa)
foobar.PaidAmount += (levelFiveItem.PaidAmount ?? 0) * -1;
}

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

private static List<FooBar> getFoobars(Foo_LevelTwoItem levelTwoItem)
{
var foobars = new List<FooBar>();

var query =
from levelThreeItem in levelTwoItem.LevelThreeItems.OfType<Foo_LevelThreeItem>()
from levelFourItem in levelThreeItem.LevelFourItems.OfType<Foo_LevelFourItem>()
from levelFiveItem in levelFourItem.LevelFiveItems.OfType<Foo_LevelFiveItem>()
from levelSixItem in levelFiveItem.LevelSixItems.OfType<Foo_LevelSixItem>()
select new { LevelFiveItem = levelFiveItem, Key = levelSixItem.Key };

foreach (var info in query)
processLevelSixItem(foobars, info.LevelFiveItem, info.Key);

return foobars;
}

16
ответ дан 23 февраля 2011 в 03:02 Источник Поделиться

Я бы, наверное, рефакторинг две сокровенные циклы в метод, возвращающий FooBarCollection. Вы могли бы затем свернуть на другой петельки с LINQs метода SelectMany:

foreach (var level2 in rootObject.SelectMany(l1 => l1.LevelTwoItems)) {
var items = level2.LevelThreeItems
.SelectMany(l3 => l3.LevelFourItems)
.SelectMany(l4 => l4.LevelFiveItems);
yield return CollectFooBars(level2, items);
}

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

5
ответ дан 23 февраля 2011 в 01:02 Источник Поделиться

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

Сделать структуру дерева явные

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

Мы можем иметь класс вот так :

public static class BranchExtensions
{
public static Dictionary<Type, Func<object, IEnumerable>> NextLevels = new Dictionary<Type, Func<object, IEnumerable>> ();

private static IEnumerable NextLevel ( object branch )
{
Func<object, IEnumerable> nextLevel;
if (NextLevels.TryGetValue(branch.GetType (), out nextLevel))
return nextLevel ( branch );
else
return null;
}
}

Тогда нам нужно где-то код инициализации :

BranchExtensions.NextLevels.Add ( typeof ( Foo_LevelOneItem ), level => ( (Foo_LevelOneItem) level ).Level2Items );
BranchExtensions.NextLevels.Add ( typeof ( Foo_LevelTwoItem ), level => ( (Foo_LevelTwoItem) level ).Level3Items );

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

Напишите некоторые общие служебные функции

Это позволяет нам написать какой-то общий код.

Что нам нужно сделать, это пройти по дереву, собирая объекты, мы хотим по пути. Когда мы читаем листе, мы хотим обработать.

Поэтому мы добавим к нашей BranchExtensions :

    private static IEnumerable<Dictionary<Type, object>> Flatten ( object branch, Predicate<object> collect, Dictionary<Type, object> collection )
{
IEnumerable nextLevel = NextLevel ( branch );
if ( nextLevel != null )
{
foreach ( object next in nextLevel )
{
if ( next != null )
{
// Do we want to collect this type
if ( collect ( next.GetType () ) )
collection[next.GetType ()] = next;

if ( NextLevel ( next ) == null )
{
// This is a leaf node.
yield return collection;
collection.Remove (next.GetType ());
}
else
{
// This is a branch, so recurse down the tree.
foreach ( var more in Flatten ( next, collect, collection ) )
{
if ( more != null )
{
yield return more;
collection.Remove ( more.GetType () );
}
}
}
}
}
}
}

public static IEnumerable<Dictionary<Type, object>> Flatten ( this Predicate<object> collect, object branch )
{
return Flatten ( branch, collect, new Dictionary<Type, object> () );
}

public static Predicate<object> Collect ( Predicate<object> collect )
{
return collect;
}

Первое разогнуть способ делает всю работу. Это довольно ужасным, и я уверен, что это может быть прибраны и рефакторинга, я просто взломал его вместе, чтобы продемонстрировать точку.

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

Как небольшое жабо - это метод расширения сказуемого. Метод collect следующим еще более странным и ничего не делает на самом деле. Это просто есть, чтобы позволить более свободно интерфейс, когда приходят, чтобы использовать его.

Используйте код

Вложенные циклы могут быть преобразованы в что-то вроде следующего :

foreach ( var collection in BranchExtensions.Collect( type => type == typeof(Foo_LevelFiveItem) || type == typeof(Foo_LevelSixItem)).Flatten ( rootObject ) )
{
var levelFiveItem = collection[typeof(Foo_LevelFiveItem)] as Foo_LevelFiveItem;
var levelSixItem = collection[typeof(Foo_LevelSixItem)] as Foo_LevelSixItem

// Do stuff on our items...
}

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

Но,

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

4
ответ дан 23 февраля 2011 в 03:02 Источник Поделиться

Если вы извлеките внутренние вложенные участие как собственный метод, который принимает LevelSixKey в качестве параметра, а затем создать свой собственный перечислителя по levelSixItems, продукт которого такой же, как и все ваши вложения (и, честно говоря, просто перенесли все вложения в него - или добавить в список внутри самого внутреннего цикла, и вернулся в тот список по счетчикам), то что вы видите на фрагментик-здание просто итерации по счетчикам и то, что вы делаете - и все уродливые вложенные вещи спрятаны с глаз долой. Это не решит проблему, но, может, это делает его более управляемым.

2
ответ дан 22 февраля 2011 в 10:02 Источник Поделиться