Глубокое вложение при переходе по объектной модели из третьей части

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

Код заканчивается таким образом (с изменением имен переменных и типов). Как очистить этот беспорядок, когда я не могу изменить структуру 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
                         };
    }
}
11 голосов | спросил CaffGeek 23 FebruaryEurope/MoscowbWed, 23 Feb 2011 00:41:01 +0300000000amWed, 23 Feb 2011 00:41:01 +030011 2011, 00:41:01

5 ответов


16

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

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 или from, но, честно говоря, в вашем конкретном случае я оставил бы это так. Это очень ясно. Если вы все еще хотите синтаксис запроса, вот только 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;
}
ответил Timwi 23 FebruaryEurope/MoscowbWed, 23 Feb 2011 06:57:16 +0300000000amWed, 23 Feb 2011 06:57:16 +030011 2011, 06:57:16
5

Я бы, вероятно, реорганизовал два внутренних цикла в метод, возвращающий 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);
}

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

ответил gix 23 FebruaryEurope/MoscowbWed, 23 Feb 2011 04:07:32 +0300000000amWed, 23 Feb 2011 04:07:32 +030011 2011, 04:07:32
4

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

Сделать ядро ​​структуры

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

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

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 );

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

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

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

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

Итак, добавим следующее к нашим 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;
    }

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

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

Как легкий оборка - это метод расширения на Predicate. Следующий метод 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...
}

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

Но,

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

ответил Mongus Pong 23 FebruaryEurope/MoscowbWed, 23 Feb 2011 18:43:49 +0300000000pmWed, 23 Feb 2011 18:43:49 +030011 2011, 18:43:49
2

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

ответил Carl Manaster 23 FebruaryEurope/MoscowbWed, 23 Feb 2011 01:15:23 +0300000000amWed, 23 Feb 2011 01:15:23 +030011 2011, 01:15:23
0

Я не тестировал его правильно, но этот запрос LINQ должен сделать то же самое:

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

        var foobars = (from levelThreeItem in levelTwoItemsItem.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>()
                       group levelSixItem by levelSixItem.levelSixKey into groupedLevelSixItems
                       select new FooBar() {
                           LevelSixKey = groupedLevelSixItems.Key,
                           TransDate = levelFiveItem.TransDate,
                           PaidAmount = (-levelFiveItem.PaidAmount ?? 0) * groupedLevelSixItems.Count()
                       }).ToList();

        yield return new FooBarsCollection
                         {
                             Prop1 = levelTwoItemsItem.Prop1,
                             Prop2 = levelTwoItemsItem.Prop2,
                             Prop3 = levelTwoItemsItem.Prop3,
                             FooBars = foobars
                         };
    }
}
ответил ICR 26 MarpmSat, 26 Mar 2011 19:04:23 +03002011-03-26T19:04:23+03:0007 2011, 19:04:23

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132