Search code examples
c#linqfor-looprecursiondto

Modifiying IEnuemerable in for loops


How can I modify the nested elements inside of an IEnuemerable<ProductLineDto>

So basically the loops look like this product lines > project types > projects > sub projects > which consists of more sub projects and activities.

The problem is I can't modify the activities of a subproject which is supposed to be happening in the recursive function GetActivitiesForActivityPerson

        private IEnumerable<ProductLineDto> GetGanttDataByPerson(IEnumerable<ProductLineDto> ganttData, GanttFilterDto ganttFilterDataModel)
        {
            if(ganttFilterDataModel.PersonId == null)
                return ganttData;

            var gdata = ganttData.CopyProductLineDtosEnumerable().ToList();

            for(var i = 0; i < gdata.Count; i++)
            {
                    var pType = gdata[i].ProjectType.CopyProjectTypeDtoEnumerable().ToList();

                    for (var j = 0; j < pType.Count; j++)
                    {
                        var projects = pType[j].Projects.CopyProjectDtosEnumerable().ToList();
                        for (var a = 0; a < projects.Count; a++)
                        {
                            // projects[a].Children is not being modified
                            projects[a].Children = GetActivitiesForActivityPerson(projects[a].Children, ganttFilterDataModel.PersonId);
                        }
                        pType[j].Projects = projects;
                    }

                gdata[i].ProjectType = pType;
            }

            ganttData = gdata;
            return ganttData; ;
        }

here is the recursive function

    private IEnumerable<SubProjectDto> GetActivitiesForActivityPerson(IEnumerable<SubProjectDto> children, Guid? personId)
    {
        var subProjects = children.CopySubProjectDtoEnumerable().ToList();

        for (var i = 0; i < subProjects.Count; i++)
        {
            var acts = subProjects[i].Activities.CopyActivityDtoEnumerable().ToList();

            acts.RemoveAll(activity => activity.ActivityPersons.All(person => person.PersonID != personId));

            subProjects[i].Activities = acts;

            if(subProjects[i].Children.Any())
                GetActivitiesForActivityPerson(subProjects[i].Children, personId);
        }

        children = subProjects;
        return children;
    }

When I step through the recursive function, the return children statement consists of the modified activities. However, this line of code does not consist of the modified activities it consists of the original data set after the assignment call.

projects[a].Children = GetActivitiesForActivityPerson(projects[a].Children, ganttFilterDataModel.PersonId);

If anyone is wondering what all of the copy methods look like, They just return a new instance of the DTO.

for example

    public static IEnumerable<ProjectTypeDto> CopyProjectTypeDtoEnumerable(this IEnumerable<ProjectTypeDto> projectTypes)
    {
        return projectTypes.Select(x => new ProjectTypeDto
        {
            ProjectTypeId = x.ProjectTypeId,
            Description = x.Description,
            RowId = x.RowId,
            Projects = x.Projects.CopyProjectDtosEnumerable()
        });
    }

Solution

  • For future reference, please submit an executable excerpt of code. For best results, you want people to be able to copy-past your problem code straight into their IDEs to play with.

    Also, the code was painfully convoluted. It simplifies to this:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace StackOverflow_LoopMadness
    {
        class Program
        {
            static void Main(string[] args)
            {
                // ???
            }
    
            private static IEnumerable<ProductLineDto> GetGanttDataByPerson(IEnumerable<ProductLineDto> ganttData, GanttFilterDto ganttFilterDataModel)
            {
                if (ganttFilterDataModel.PersonId == null)
                    return ganttData;
    
                foreach (var pType in ganttData)
                {
                    foreach (var project in pType.Projects)
                    { 
                        project.Children = GetActivitiesForActivityPerson(project.Children, ganttFilterDataModel.PersonId);
                    }
                }
                return ganttData;
            }
    
            private static IEnumerable<SubProjectDto> GetActivitiesForActivityPerson(IEnumerable<SubProjectDto> children, Guid? personId)
            {
                foreach (var subProject in children)
                {
                    subProject.Activities = subProject.Activities.Where(a => a.ActivityPersons.All(person => person.PersonID != personId));
    
                    if (subProject.Children.Any())
                    {
                        GetActivitiesForActivityPerson(subProject.Children, personId);
                    }
                }
                return children;
            }
        }
    
        class SubProjectDto
        {
            public IEnumerable<Activity> Activities { get; internal set; }
            public IEnumerable<SubProjectDto> Children { get; internal set; }
        }
    
        class Activity
        {
            public IList<ActivityPerson> ActivityPersons { get; internal set; }
        }
    
        class ActivityPerson
        {
            public Guid? PersonID { get; internal set; }
        }
    
        class GanttFilterDto
        {
            public Guid PersonId { get; internal set; }
        }
    
        class ProductLineDto
        {
            public IList<Project> Projects { get; internal set; }
        }
    
        class Project
        {
            public IEnumerable<SubProjectDto> Children { get; set; }
        }
    }
    

    As you can see, swapping the for-loops with foreach-loops greatly decreases the amount of code and increases readability. The extraneous copy lists were removed. Moreover, it was necessary for me to create 6 data classes via guesswork in order to compile.

    I gave up figuring out a suitable driver for this program. However, I'm confident the problem of modifying nested elements is solved, because the edited variables are only passed by reference.

    I think your problem stems from an incomplete understanding of pass by value vs. reference, thus your confusing use of deep copies. Microsoft can explain it better than me, so please check out this link.