Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ results change at end of for loop

Tags:

c#

linq

When performing a set of LINQ queries against a data-source (I'm using LINQ-to-SQL, but it happens here too using just a List<string> object), I end up getting a different result at the end of my checks.

Specifically, the code below is trying to find if a Fully Qualified Domain Name (FQDN) either exists in a list of host names (not all of which will be FQDNs or in the same domain, but the host identifier is what matters to me). The search is trying to find whether "host-6.domain.local" or any of its sub-components exist (i.e, "host-6.domain" or "host-6") in the list, which they do not. While inside the for-loop, we get the results we expect, but as soon as the for loop is finished, I get a result that has all of the contents of the list, which to me sounds like it is trying to find elements that match the empty string.

void MyMethod()  
{  
    string fqdn = "host-6.domain.local";  
    string[] splitFqdn = fqdn.Split('.');  
    List<string> values = new List<string>();  
    values.add("host-1");  
    values.add("host-2.domain.local");  
    values.add("host-3.domain.local");  
    values.add("host-4");  
    values.add("host-5.other.local");  
    IEnumerable<string> queryResult = null;  
    for (int i = splitFqdn.Length; i > 0; i--)  
    {  
        result =  
            from value in values  
            where value.StartsWith(  
                string.Join(".", splitFqdn.Take(i)))  
            select value;  
        Console.WriteLine(  
            "Inside for loop, take " + i + ": "  + result.Count());  
    }  
    Console.WriteLine();  
    Console.WriteLine(  
        "Outside for loop: " + result.Count());  
}

Why is this happening and how can I get accurate results that I can still access after the for loop is finished?

like image 971
Sam Avatar asked Mar 21 '23 03:03

Sam


1 Answers

You are getting bitten by LINQ's lazy execution and closure.

When you create an enumerable like you are doing here...

result =  
    from value in values  
    where value.StartsWith(  
        string.Join(".", splitFqdn.Take(i)))  
    select value;  

It doesn't get evaluated until you do something that forces it to get evaluated... for instance when you do result.count()

Then later outside of your loop when you evaluate it again result.count() is evaluated with the last value of i that existed in your for loop which is not giving you what you want.

Try forcing evaluation by doing .ToList() on your enumerable like so... This code shows both values so you can compare.

void MyMethod()  
{  
    string fqdn = "host-6.domain.local";  
    string[] splitFqdn = fqdn.Split('.');  
    List<string> values = new List<string>();  
    values.add("host-1");  
    values.add("host-2.domain.local");  
    values.add("host-3.domain.local");  
    values.add("host-4");  
    values.add("host-5.other.local");  
    IEnumerable<string> queryResult = null;
    List<string> correctResult = null;
    for (int i = splitFqdn.Length; i > 0; i--)  
    {  
        queryResult =  
            from value in values  
            where value.StartsWith(  
                string.Join(".", splitFqdn.Take(i)))  
            select value;
        correctResult = queryResult.ToList();
        Console.WriteLine(  
            "Inside for loop, take " + i + ": "  + queryResult.Count());  
    }  
    Console.WriteLine();  
    Console.WriteLine(  
        "Outside for loop queryResult: " + queryResult.Count());  
    Console.WriteLine(  
        "Outside for loop correctResult: " + correctResult.Count());  
}

EDIT: Thanks nlips for pointing out that I hadn't fully answered the question... and apologies for converting to method syntax but it would have taken longer to convert to query syntax.

void MyMethod()  
{
    string fqdn = "host-6.domain.local";
    string[] splitFqdn = fqdn.Split('.');
    List<string> values = new List<string>();
    values.Add("host-1");
    values.Add("host-2.domain.local");
    values.Add("host-3.domain.local");
    values.Add("host-4");
    values.Add("host-5.other.local");
    values.Add("host-5.other.local");
    IEnumerable<string> queryResult = null;
    List<string> correctResult = new List<string>();
    for (int i = splitFqdn.Length; i > 0; i--)
    {
        correctResult = correctResult
            .Union(values.Where(
                value => value.StartsWith(string.Join(".", splitFqdn.Take(i)))))
            .ToList();
    }
}
like image 52
Kevin Avatar answered Apr 01 '23 07:04

Kevin