English 中文(简体)
Hierarchical structure iteration and LINQ
原标题:

Assume that we have class

public class RMenuItem
{
    public List<RMenuItem> ChildrenItems { get; }
    public decimal OperationID { get; }
    public string Name { get; }
}

as you can see - each menuitem could have children items - as usual in menu. My task is to iterate through each items of this list and apply some action to it. Classical decision is to write recursive iteration. But I m interesting if LINQ could make my task easier? For example, I suppose that we can write query that can get flat list of objects, which i can iterate simply with foreach. But my attempts in this way weren t successful yet. So any help appreciated!

最佳回答

It s possible:

    public void PrintAllNames(RMenuItem rootItem)
    {
        Action<RMenuItem> print = null;
        print = m =>
            {
                Console.WriteLine(m.Name);
                m.ChildrenItems.ForEach(print);
            };
        print(rootItem);
    }

Notice how it s necessary to declare print so that print can use itself. This is directly comparable to a recursive method, which I d rather use:

    public void PrintAllNames(RMenuItem rootItem)
    {
        Console.WriteLine(rootItem.Name);
        rootItem.ChildrenItems.ForEach(PrintAllNames);
    }

(although for a more complex situation, maybe the functional solution would make the most sense)

问题回答

I suggest 2 ways of achieving this. You can opt with an utility method to get all the items or you can implement the Visitor Pattern, though it implies changing the RMenuItem class.

Utility method:

    static IEnumerable<RMenuItem> GetAllMenuItems(IList<RMenuItem> items)
    {
        if (items == null)
            throw new ArgumentNullException("items");

        Queue<RMenuItem> queue = new Queue<RMenuItem>(items);

        while (queue.Count > 0)
        {
            var item = queue.Dequeue();
            if (item.ChildrenItems != null)
            {
                foreach (var child in item.ChildrenItems)
                {
                    queue.Enqueue(child);
                }
            }
            yield return item;
        }
    }

I prefer an imperative way to a recursive because we can use iterator blocks.

Visitor Pattern:

    public interface IRMenuItemVisitor
    {
        void Visit(RMenuItem item);
    }

    public class PrintRMenuItemVisitor : IRMenuItemVisitor
    {
        public void Visit(RMenuItem item)
        {
            Console.WriteLine(item);
        }
    }

    public interface IRMenuItem
    {
        void Accept(IRMenuItemVisitor visitor);
    }

    public class RMenuItem : IRMenuItem
    {
        // ...

        public void Accept(IRMenuItemVisitor visitor)
        {
            visitor.Visit(this);
            if (ChildrenItems != null)
            {
                foreach (var item in ChildrenItems)
                {
                    item.Accept(visitor);
                }
            }
        }
    }

Usage:

        RMenuItem m1 = new RMenuItem
        {
            Name = "M1",
            ChildrenItems = new List<RMenuItem> { 
                new RMenuItem { Name = "M11" }, 
                new RMenuItem { 
                    Name = "M12", 
                    ChildrenItems = new List<RMenuItem> {
                        new RMenuItem { Name = "M121" },
                        new RMenuItem { Name = "M122" }
                    }
                } 
            }
        };

        RMenuItem m2 = new RMenuItem
        {
            Name = "M2",
            ChildrenItems = new List<RMenuItem> { 
                new RMenuItem { Name = "M21" }, 
                new RMenuItem { Name = "M22" }, 
                new RMenuItem { Name = "M23" } 
            }
        };

        IList<RMenuItem> menus = new List<RMenuItem> { m1, m2 };
        foreach (var menu in GetAllMenuItems(menus))
        {
            Console.WriteLine(menu);
        }

        // or

        IList<RMenuItem> menus = new List<RMenuItem> { m1, m2 };
        foreach (var menu in menus)
        {
            menu.Accept(new PrintRMenuItemVisitor());
        }

You could difine a Flatten method in your class (or as an extension if you prefer) like this

public IEnumerable<RMenuItem> Flatten()
{
    foreach (var item in ChildrenItems)
    {
        yield return item;
    }
    return ChildrenItems.SelectMany(item => item.Flatten());
}

then doing somthing with each elements will be as simple as

RMenuItem rootItem ;

    // do somthing with the root item
    foreach (var item  in rootItem.Flatten())
    {
        // do somthing
    }

Indeed you can do that using LINQ, SelectMany flats out the list, just some example

menuItemsList.SelectMany(x => x.ChildrenItems).Where(c => c.someChildProperty);

Thanks

Edit:

In response to the comments, I was just giving an example of SelectMany previously. Thanks for pointing out.

menuItemsList.SelectMany(x => x.ChildrenItems.Select(p => p)).Where(c => c.someChildProperty);

OR something like this

menuItemsList.SelectMany(x => x.ChildrenItems).Select(p => p).Where(c => c.someChildProperty);

Edit2

Ahh .. now I understood what you want ..

We can just slightly modify my above query to do what you want

menuItemsList
.SelectMany(x => { //do something with x like printing it  
                    x.ChildrenItems 
                 })
.Select(p => { // do something with p like printing it 
                  p 
             });

Basically you can do what you want the element inside the {}

Thanks





相关问题
IEnumerable to array of parameter

Using linq? and XML is there a way to convert this IEnumerable to a string array of the value parameter? List<string> idList = new List<string>(); foreach (XElement idElement in word....

linq query for tag system - search for multiple tags

I have two tables, Tags(tagid, postid, tagname) and posts(postid, name, ...) now i want to make a query that returns me all posts that have a generic amount of tags. like: i want all posts that have ...

Linq operations against a List of Hashtables?

I m working with a set of legacy DAO code that returns an IList, where each Hashtable represents the row of a dynamically executed SQL query. For example, the List might contain the following records/...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

How to filter duplicate list items

i have list of items IList with data that looks list this: GenId TestMode 1 0 1 1 3 0 3 1 4 NULL 2 NULL i want to remove the index ...

C# Grouping/Sorting a Generic List<> using LINQ

Im looking to group and sort a Generic List<>. I have a list of objects representing files and each of these objects has a FileName, FileType and FileDate property. FileType is defined as an ...

热门标签