1

Given the following code, is there a better way to structure this?

foreach(Thing item in SomeRandomList)
{
    bool firstCondition = CalculateBool(item, someValue);
    bool secondCondition = CalculateBool(item, someOtherValue);

    //General things are done...
    if(firstCondition || secondCondition)
    {
        //semi-specific things are done...
        if(firstCondition)
        {
            //specific things are done...
        } 
        else
        {
            //specific things are done...
        }
    }
}

Also, what if there are more conditions, i.e. 3:

foreach(Thing item in SomeRandomList)
{
    bool firstCondition = CalculateBool(item, someValue);
    bool secondCondition = CalculateBool(item, someOtherValue);
    //imagine as many more as you want.
    bool nthCondition = CalculateBool(item, lastOtherValue);

    //General things are done...
    if(firstCondition || secondCondition || nthCondition)
    {
        //semi-specific things are done...
        if(firstCondition)
        {
            //specific things are done...
        } 
        else if(secondCondition)
        {
            //specific things are done...
        }
        else
        { 
            //specific things are done...
        }
    }
}
jball
  • 24,791
  • 9
  • 70
  • 92
  • 3
    The outer condition seems redundant in any case – Simon Fox Feb 18 '10 at 01:28
  • If you want particular lines to execute for certain combinations of conditions, you're stuck with spaghetti. But as for your example, Simon is right — your conditions are mutually exclusive and prioritized, so just don't bother OR ing them together in the first place. – Potatoswatter Feb 18 '10 at 01:59
  • You haven't provided enough information to say what you're trying to accomplish so how can we say if there's a better way to organize your code? – Great Turtle Feb 18 '10 at 02:00
  • @Simon, would you prefer that `//semi-specific things are done...` gets repeated in each of the remaining `if` statements? – jball Feb 18 '10 at 06:41
  • @Great Turtle, a general question is often the only way to discover a general principal as opposed to a specific solution. – jball Feb 18 '10 at 06:43
  • All I'm saying is that the outer OR'd condition is redundant because you follow by testing for more specific cases which from what you have provided are independent of the result of the outer OR – Simon Fox Feb 18 '10 at 20:16
  • So your preference would be for an `if` block for the combined `OR`, and then an independent `if`-`else if`-`else` block for the specific cases? I agree with that in many cases. – jball Feb 18 '10 at 20:43

6 Answers6

6

Yes: Polymorphism.

Derive Thing's from a common base (or define an Interface that all Thing's implement)

Failing that, move the conditional testing code into a method on Thing.

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • 2
    It's quite likely that `condition` has nothing to do with type. You might handle two ranges of numbers differently with such a structure, but never create a type just for small numbers. – Potatoswatter Feb 18 '10 at 01:56
3

If you can do the semi-specific things after the specific ones, you could try this:

bool anyTrue = true;

if (firstCondition)
{
    // Specific things
}
else if (secondCondition)
{
    // Specific things
}
else
{
    // Specific things
    anyTrue = false;
}

if (anyTrue)
{
    // Semi-specific things
}

I don't know if it's necessarily better, but it's different...

Alternatively, I'm not all up on C# 3.0 and the fancy new LINQ stuff, but if its expressive enough you could try something like (pseudo-code):

bool[] conditions =
{
    CalculateBool(item, someValue),
    CalculateBool(item, someOtherValue),
    ...
};

if (conditions.AnyTrue)
{
    switch (conditions.IndexOfFirstTrueItem)
    {
        case 0:
            // Specific things
            break;

        case 1:
            // Specific things
            break;

        default:
            // Specific things
            break;
    }
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
2

I'd use some LINQ to use an intermediate query to help reduce the logic in the loop:

// Get condition in the query.
var query =
    from item in SomeRandomList
    let condition1 = CalculateBool(item, someValue1)
    let condition2 = CalculateBool(item, someValue2)
    ...
    let conditionN = CalculateBool(item, someValueN)
    where
        condition1 || condition2 || ... || conditionN
    select new { Item = item, 
        Condition1 = condition1, Condition1 = condition2, ...
        ConditionN = conditionN };

foreach(var item in query) 
{ 
    //semi-specific things are done... 
    if(firstCondition) 
    { 
        //specific things are done... 
    }  
    else 
    { 
    //specific things are done... 
    } 
}

Hopefully, this will reduce the amount of code in the loop tremendously.

It seems to me though that you have a sequence of values that are being passed to CalculateBool for each item in SomeRandomList. If that is the case, then you could easily generate a query which does a cross join and filter on that:

// Get all valid items across all values.
var query =
    from item in SomeRandomList
    from value in someValues
    where CalculateBool(item, value)
    select { Item = item, Value = value };

// Iterate and process.
foreach (var item in query)
{
    // Use item.Item and item.Value.
    // Specifically, use item.Value to perform a lookup
    // in a map somewhere to determine the course of action
    // instead of a giant switch statement.
}

This would work because your conditionals indicate that you would only have one value set for each item.

casperOne
  • 73,706
  • 19
  • 184
  • 253
2

I like the approach of having a dictionary of Predicate<T> and their associated Actions. I answered a similar question here:

Coming out of the habit of writing ifs/elseifs for every possible condition

To modify it a bit for your question:

Dictionary<Predicate<Something>, Action> mappings = {{...}}
bool shouldDoAnything = mappings.Keys.Aggregate(true, (accum, condition) => 
    accum || condition);

if (shouldDoAnything)
{
   //do semi-specific things

   foreach(DictionaryEntry<Predicate<Something>, Action> mapping in mappings)
   {
      if (mapping.Key(input))
      {
         mapping.Value(); //do specific things
         break;
      }
    }
}
Community
  • 1
  • 1
0

I might have not been able to get the question right, we can only have 2 results, if function has return type bool not n results, unless it is Nullable<bool>, which could return null as well.

so

bool result = CalculateBool(item, someValue);   

if(result) {}
else {} 

will do it.

about managing large if / else combination ? One way is to use Switch statement, that could increase readability.

But in any case, a method should always have least possible decision paths, this is known as

If this happens, split the code into more appropriate methods

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Asad
  • 21,468
  • 17
  • 69
  • 94
0
foreach(Thing item in SomeRandomList)
{
    DoGeneralThings(); //pass in whatever your require to the method

    if(FirstCondition(item, someValue))
    {
        DoThingsWhenAnyConditionIsTrue(); //pass in whatever your require to the method 
        DoSpecificThingsForFirstCondition(); //pass in whatever your require to the method
        continue;
    } 

    if(SecondCondition(item, someValue))
    {
        DoThingsWhenAnyConditionIsTrue(); //pass in whatever your require to the method 
        DoSpecificThingsForSecondCondition(); //pass in whatever your require to the method
        continue;
    } 
}
Nog
  • 1