开发者

Nesting conditionals in loops

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
        { 
     开发者_如何学C       //specific things are done...
        }
    }
}


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.


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;
    }
}


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.


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;
      }
    }
}


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;
    } 
}


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

  • cyclomatic complexity.

If this happens, split the code into more appropriate methods

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜