views:

87

answers:

2

I have a piece of code for some validation logic, which in generalized for goes like this:

private bool AllItemsAreSatisfactoryV1(IEnumerable<Source> collection)
{
    foreach(var foo in collection)
    {
        Target target = SomeFancyLookup(foo);
        if (!target.Satisfactory)
        {
            return false;
        }
    }
    return true;
}

This works, is pretty easy to understand, and has early-out optimization. It is, however, pretty verbose. The main purpose of this question is what is considered readable and good style. I'm also interested in the performance; I'm a firm believer that premature {optimization, pessimization} is the root of all evil, and try to avoid micro-optimizing as well as introducing bottlenecks.

I'm pretty new to LINQ, so I'd like some comments on the two alternative versions I've come up with, as well as any other suggestions wrt. readability.

private bool AllItemsAreSatisfactoryV2(IEnumerable<Source> collection)
{
    return null ==
        (from foo in collection
        where !(SomeFancyLookup(foo).Satisfactory)
        select foo).First();
}

private bool AllItemsAreSatisfactoryV3(IEnumerable<Source> collection)
{
    return !collection.Any(foo => !SomeFancyLookup(foo).Satisfactory);
}

I don't believe that V2 offers much over V1 in terms of readability, even if shorter. I find V3 to be clear & concise, but I'm not too fond of the Method().Property part; of course I could turn the lambda into a full delegate, but then it loses it's one-line elegance.

What I'd like comments on are:

  1. Style - ever so subjective, but what do you feel is readable?
  2. Performance - are any of these a definite no-no? As far as I understand, all three methods should early-out.
  3. Debuggability - anything to consider?
  4. Alternatives - anything goes.

Thanks in advance :)

+9  A: 

I think All would be clearer:

private bool AllItemsAreSatisfactoryV1(IEnumerable<Source> collection)
{
    return collection.Select(f => SomeFancyLookup(f)).All(t => t.Satisfactory);
}

I think it's unlikely using linq here would cause a performance problem over a regular foreach loop, although it would be straightforward to change if it did.

Lee
Agreed - no need for negations, and it still quits out early if it finds an unsatisfactory item.
Joel Mueller
In .NET 4, you can simplify it even further: `return collection.Select(SomeFancyLookup).All(t => t.Satisfactory);`
Joel Mueller
@Joel - The same syntax is legal in 3.5; SomeFancyLookup becomes a delegate with a known method signature.
KeithS
@KeithS - I think it depends. The syntax may be legal, but it's more likely to successfully compile in C# 4. I've had cases where the C# 3 compiler couldn't infer the method signatures in situations like that unless I inserted a lambda, but the covariance/contravariance support in C# 4 allowed it to work.
Joel Mueller
+1. I still need to get used to chaining, but this is pretty short and sweet.
snemarch
Is the projection really necessary? I'd write it like this: `return collection.All(f => SomeFancyLookup(f).Satisfactory);`
Jeff M
@Jeff - The projection definitely shouldn't hurt; maybe a couple layers on the call stack.
KeithS
+1  A: 

I personally have no problem with the style of V3, and that one would be my first choice. You're essentially looking through the list for any whose lookup is not satisfactory.

V2 is difficult to grasp the intent of, and in its current form will throw an exception (First() requires the source IEnumerable to not be empty; I think you're looking for FirstOrDefault()). Why not just tack Any() on the end instead of comparing a result from the list to null?

V1 is fine, if a bit loquacious, and probably the easiest to debug, as I've found debugging lambdas to be a bit persnickety at times. You can remove the inner braces to lose some whitespace without sacrificing readability.

Really, all three will boil down into very similar opcodes; iterate through collection, call SomeFancyLookup(), and check a property of its return value; get out on the first failure. Any() "hides" a very similar foreach algorithm. The difference between V1 and all others is the use of a named variable, which MIGHT be a little less performant, but you have a reference to a Target in all three cases so I doubt it's significant, if a difference even exists.

KeithS
"Why not just tack Any() on the end" - d'oh! Thanks for the exception warning as well. Very similar IL code is indeed generated for the two LINQ versions, with the "natural language" version having one extra call.
snemarch