4

having some doubts with LINQ queries over objects...

I have this filter descriptions which I want to keep private, and at given checkpoints in the code, I set some flags active. At the end of processing, I want to filter the active flags.

If any flags are active, I want to write them to the console. (I want all flagged checkpoints in one string so I can perhaps change the code to throw an exception instead later on).

This is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;

namespace checkpoints
{
    public class Program
    {
        public static void Main(string[] args)
        {            
            var filters = new Filters().FilterList;

            filters[0].flag = true;
            filters[2].flag = true;

            var query = filters.Where(f => f.flag).Select(f => f.desc); 
            Console.WriteLine("Filter points active: ");
            string fpoints = System.String.Empty;

            foreach(string fp in query)
                { fpoints = fpoints + fp + System.Environment.NewLine; }

            Console.WriteLine(fpoints);
        }
    }
    public class Filters
    {        
        public List<Filter> FilterList = new List<Filter>{};

        public Filters()
        {
           foreach(var def in Filters.def_desc)               
               { this.FilterList.Add(new Filter(false, def)); }
        }

        private readonly static string[] def_desc = new string[3]
        { "Filter AX2123: Failed file write.", 
          "Filter XVB231: Failed table load.", 
          "Filter FZD358: Transaction halted." };

        public class Filter
        {
            public bool flag;
            public readonly string desc;       

            public Filter(bool flag, string desc)
            {
                this.flag = flag;
                this.desc = desc;
            }
        }            

    }
}

It works. My issue is with the need to run a for each over the LINQ query results (which is a IEnumerable<Filter> I guess) in order to extract the strings. If I already run a query (i.e., went all over the code) why do I need to "run again"? Looks like it scales terrible and is not very elegant... Any thoughts?

DaveShaw
  • 52,123
  • 16
  • 112
  • 141
Ren85
  • 65
  • 7

6 Answers6

2

The call of var query = filters.Where(f => f.flag).Select(f => f.desc); does not run the query. The IEnumerable<Filter> returned by the chain of calls does not collect Filter objects until you enumerate the return results in your foreach loop.

There is an advantage and a disadvantage to this. The advantage is that you save all the CPU time needed to evaluate your query in cases when the query results are not needed. The disadvantage is that if you need to enumerate results multiple times, the second time around the system will need to recalculate the results again. The fix to address this last issue is to enumerate your query results right away, and put them into an array or a list, like this:

var queryResults = filters.Where(f => f.flag).Select(f => f.desc).ToList();
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks for your explanation. When using ToList() I only save the time to process the query multiple times, but I still need to iterate the queryResults to put them into a string, right? – Ren85 Dec 20 '11 at 00:56
  • @Ren85 Because of the `Select(f=>f.desc)` at the end of the query chain, your query returns an `IEnumerable`. Therefore, `ToList` will return a list of strings as well. – Sergey Kalinichenko Dec 20 '11 at 01:00
2

Since dasblinkenlight and Daev already mentioned the deferred execution part, I thought you'd be interested to know that you can join strings without foreach by replacing

foreach(string fp in query)
{ fpoints = fpoints + fp + System.Environment.NewLine; }

with

string.Join(Environment.NewLine, query)
Tung
  • 5,334
  • 1
  • 34
  • 41
2
string fpoint=query.Aggregate((c, c1) => c + System.Environment.NewLine+ c1 );
Sleiman Jneidi
  • 22,907
  • 14
  • 56
  • 77
1

Your query variable is an expression and will not be evaluated until it is enumerated over (by the foreach in your case). If you examine query with the debugger you will not see the contents of it as a collection of Filter's rather a query to get your results. This is called "deferred execution" and is covered pretty well in this Blog Post (although it's Linq-SQL the same principals apply).

DaveShaw
  • 52,123
  • 16
  • 112
  • 141
1

If you want to get rid of your foreach, here's something goofy you can try.

string.Join(Environment.NewLine, query.ToArray());

Apologies if I didn't read your question close enough :)

hawkke
  • 4,242
  • 1
  • 27
  • 23
0

What you are after is the LINQ function called aggregate. It will join list items together into a single output.

A quick google shows this stackoverflow post showing how to use the aggregate function: Linq Aggregate function

A simple example is for using the function is here: http://blueonionsoftware.com/blog.aspx?p=509fa3f8-c125-4e61-9227-8ca4c4e29210 .This example uses commas, where you can instead use new lines.

Community
  • 1
  • 1
Dessus
  • 2,147
  • 1
  • 14
  • 24