Kilazur
Kilazur

Reputation: 3188

Nested foreach conversion to LINQ with a condition on two lists

LINQ is like voodo magic to me. No need to show you what I've done, nothing works or even compiles, I'm just getting error in a somewhat not understandable language:

The type arguments for method 'System.Linq.Enumerable.SelectMany<TSource,TResult>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,int,System.Collections.Generic.IEnumerable<TResult>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

So, here's my code:

foreach (string first in firstStrings)
    foreach (string second in secondStrings)
    {
        try {
            if (second.Contains(DoFormatting(first)))
                DoStuff(first, second);
        }
        catch (Exception e)
        {
            LogStuff(first, second);
        }
    }

(How) can I translate it to Linq?

Upvotes: 0

Views: 1196

Answers (5)

Oren Hizkiya
Oren Hizkiya

Reputation: 4434

If you really want to you could create an extension method as below:

    public static class ExtensionMethods
    {
        public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
        {
            foreach (T item in enumeration)
            {
                action(item);
            }
        }
    }

and then use LINQ as follows:

 firstStrings.ForEach(first => secondStrings.ForEach(second =>
                {
                    try
                    {
                        if (second.Contains(DoFormatting(first)))
                        {
                            DoStuff(first, second);
                        }
                    }
                    catch(Exception e)
                    {
                        LogStuff(first, second);
                    }
                }));

But, as other commentors have mentioned, I would keep your code as is (at least in regard to whether you convert it to LINQ).

Upvotes: 2

stevenrcfox
stevenrcfox

Reputation: 1567

The issue here is that LINQ was designed using principles of functional programming, and the big principle there is to not cause side effects.

That said there are some things you can do, using a combination of juharr and Erik's approaches this would work:

        var ContainedPairs=
            firstStrings.Join(secondStrings, 
                              x => true, 
                              y => true, 
                             (first, second) => new { first, second })
                         .Where(pairs => pairs.second.Contains(DoFormatting(pairs.first)));

//loop over pairs calling dostuff and error handling

Note that the important thing here is that DoFormatting must be guaranteed not to change pairs.first, and not to throw an error.

If this is not the case then changing the signature of DoFormatting to the following:

private bool ContainsFormatted(string first, string second, out Exception ex)

would allow you to do this:

        var pairResults =
            firstStrings.Join(secondStrings, 
                              x => true, 
                              y => true, 
                              (first, second) => new { first, second })
                        .Select(pair =>
                                {
                                    Exception ex = null;
                                    var containsFormatted = ContainsFormatted(pair.second, pair.first, out ex);
                                    return new { pair, containsFormatted, ex };
                                });
//loop over pair results, calling DoStuff on items where containsFormatted = true and logging exceptions etc otherwise

The point here is that side effects and error handling are still left out of linq, but that doesn't mean that you need to give up the benefits of a more functional style of programming

Upvotes: 1

Erik Funkenbusch
Erik Funkenbusch

Reputation: 93444

You can't really translate much of it to Linq because you still have to iterate over all of the permutation, which requires two separate nested loops.

You could do this, it doesn't save a lot, but some:

foreach (string first in firstStrings)
    try {
        foreach (var second in secondStrings.Where(second => second.Contains(DoFormatting(first))))
           DoStuff(first, second);
    }
    catch (Exception e) {
        LogStuff(first, second);
    }

EDIT:

Note that the logging here would have to change because second is not available in the catch block, which is a problem with moving things to linq.. you have less access to which item in the collection caused the error. You would probably have to move the exception handling into both DoFormtting and DoStuff.

And, as juharr pointed out, if an exception is thrown it will terminate processing of secondStrings rather than continuing to the next. So that may not be the desired function either.. again, a side effect of moving to linq, you lose the ability to do fine grained exception handling and continuation.

Upvotes: 1

juharr
juharr

Reputation: 32296

You could do the following to reduce it down to one foreach, but honestly you might as well leave it as is.

var pairs = from first in firstStrings
            from second in secondStrings
            select new 
            { 
                first, 
                second
            };

foreach(var pair in pairs)
{
    try 
    {
        if (pair.second.Contains(DoFormatting(pair.first)))
            DoStuff(pair.first, pair.second);
    }
    catch (Exception e)
    {
        LogStuff(pair.first, pair.second);
    }
}

OR with extension methods

var pairs = firstStrings.Join(
    secondStrings, 
    x=>true, 
    y=>true, 
    (first, second) => new { first, second});

foreach(var pair in pairs)
{
    try 
    {
        if (pair.second.Contains(DoFormatting(pair.first)))
            DoStuff(pair.first, pair.second);
    }
    catch (Exception e)
    {
        LogStuff(pair.first, pair.second);
    }
}

I would not use a ForEach extension method because enumerating over an enumeration and creating side effects is against the best practices of using Linq.

Upvotes: 2

Miniversal
Miniversal

Reputation: 129

I would recommend taking a look at Lambda expressions.

http://msdn.microsoft.com/en-us/library/bb397675.aspx

So, the LINQ translation would look something like this

String result = second.Where( r => r.Contains(first));

Upvotes: 0

Related Questions