Эффективный способ получения всех вложенных объектов определенного типа в сложном списке

#c# #list

#c# #Список

Вопрос:

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

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

Итак, у меня есть объект класса, который имеет элемент списка, который также имеет элемент списка, который также имеет элемент списка, например:

  • Тип расписания
    • Список periodType
      • Запись списка
        • Рабочий день списка

И я хочу получить все объекты типа WorkDay.

Вот пример кода. Я создал пример объекта, содержащего 4 объекта WorkDay, расположенных в дереве. Как мне эффективно извлекать их?

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class TimesheetType
    {
        public List<PeriodType> PeriodTypes { get; set; }
    }

    public class PeriodType
    {
        public List<Entry> Entrys { get; set; }
    }

    public class Entry
    {
        public List<WorkDay> WorkDays { get; set; }
    }

    public class WorkDay
    {
        public DateTime Date { get; set; }
        public double Hours { get; set; }
    }



    class Program
    {
        static void Main(string[] args)
        {
            TimesheetType timesheetType = CreateTimesheetTypeExample();

            // Get all WorkDay typed objects in the tree, should return a List<WorkDay> with 4 items in it
            //var allWorkDays = timesheetType.PeriodTypes.Select(...) 
        }

        private static TimesheetType CreateTimesheetTypeExample()
        {
            TimesheetType timesheetType = new TimesheetType
            {
                PeriodTypes = new List<PeriodType>()
                {
                    {
                        new PeriodType()
                    },
                    {
                        new PeriodType()
                    },
                    {
                        new PeriodType()
                    },
                }
            };

            timesheetType.PeriodTypes[0].Entrys = new List<Entry>()
            {
                {
                    new Entry()
                }
            };

            timesheetType.PeriodTypes[1].Entrys = new List<Entry>()
            {
                {
                    new Entry()
                }
            };

            timesheetType.PeriodTypes[2].Entrys = new List<Entry>()
            {
                {
                    new Entry()
                }
            };

            timesheetType.PeriodTypes[0].Entrys[0].WorkDays = new List<WorkDay>(1)
            {
                new WorkDay() { Date = DateTime.Parse("2020-01-01"), Hours = 3 },
                new WorkDay() { Date = DateTime.Parse("2020-01-02"), Hours = 6 }
            };

            timesheetType.PeriodTypes[2].Entrys[0].WorkDays = new List<WorkDay>(1)
            {
                new WorkDay() { Date = DateTime.Parse("2020-01-03"), Hours = 12 },
                new WorkDay() { Date = DateTime.Parse("2020-01-04"), Hours = 24 }
            };

            return timesheetType;
        }
    }
}
  

Комментарии:

1. Использование linq не делает его более эффективным ни при каком воображении. Удобно? Конечно. Циклы все равно где-то происходят, где-то это Linq.

Ответ №1:

Предполагая PeriodTypes , Entries , WorkDays (так что все списки) никогда не равны нулю (но могут быть пустыми), пара SelectMany должна сделать трюк:

 var allWorkDays = timesheetType.PeriodTypes
    .SelectMany(c => c.Entrys)
    .SelectMany(c => c.WorkDays).ToList();
  

SelectMany выравнивает вложенный список, именно то, что вы хотите сделать.

В вашем примере, хотя списки могут быть нулевыми, вам нужно отфильтровать нули:

 var allWorkDays = timesheetType.PeriodTypes.
    Where(c => c.Entrys != null).SelectMany(c => c.Entrys)
   .Where(c => c.WorkDays != null).SelectMany(c => c.WorkDays)
   .ToList();
  

Или как это:

 var allWorkDays = timesheetType.PeriodTypes
    .SelectMany(c => c.Entrys ?? new List<Entry>())
    .SelectMany(c => c.WorkDays ?? new List<WorkDay>())
    .ToList();
  

Комментарии:

1. отлично! Именно то, что я искал!