views:

354

answers:

6

I'm just starting out learning C# and I've become stuck at something very basic.

For my first "app" I thought I'd go for something simple, so I decided for a BMI calculator.

The BMI is calculated into a decimal type which I'm now trying to use in a switch statement, but aparently decimal can't be used in a switch?

What would be the C# solution for this:

            decimal bmi = calculate_bmi(h, w);

            switch (bmi) {
                case < 18.5: 
                    bmi_description = "underweight.";
                    break;
                case > 25:
                    bmi_description = "overweight";
                case > 30:
                    bmi_description = "very overweight";
                case > 40:
                    bmi_description = "extreme overweight";
                    break;
            }
+8  A: 

That is not possible with switch statements in C#.
The reason why is because each case statement requires a constant expression after it.

Also each value is allowed only once and the type of the expression must match the type in your switch. In your case that is not the case because you wanted to have bool type case statements but a decimal in your switch.

Consider refactoring using a helper function instead:

//...
decimal bmi = calculate_bmi(h, w);
string bmi_description = get_description_for_bmi(bmi);
//...

string get_description_for_bmi(decimal bmi)
{
    string desc;
    if(bmi < 18.5m)
      desc = "underweight";
    else if(bmi <= 25)
      desc = "average";//You forgot this one btw
    else if(bmi <= 30)
      desc = "overweight";
    else if(bmi <= 40)
      desc = "very overweight";     
    else
      desc = "extreme overweight";

    return desc;
}

Further reading:

Not only are range values not allowed but non constant expressions are also not allowed.

Here is an example of something that is not possible:

bool b = true;
bool y = false;
switch (b)
{
    case true:
        break;
    case y:
        break;
}

However this is possible:

bool b = true;
const bool y = false;
switch (b)
{
    case true:
        break;
    case y:
        break;
}
Brian R. Bondy
You can make it simpler : you don't need to check `bmi > 18.5`, since you're in the `else`. The same goes for the following tests . `if (bmi <= 18.5) ... else if (bmi <= 25) ... else if (bmi <= 30) ...`
Thomas Levesque
@Thomas: True thanks, fixed.
Brian R. Bondy
To *exactly* match the behavior of the code from the question it should be < 18.5 instead of <= 18.5.
Daniel Brückner
@Daniel: Thanks, fixed.
Brian R. Bondy
@phobia: Put an m after 18.5, I fixed above now. If you want a literal to be treated as a decimal you need the m.
Brian R. Bondy
@phobia: You can read more about the `m` suffix here: http://msdn.microsoft.com/en-us/library/364x0z75(VS.71).aspx
Brian R. Bondy
@Brian: Thanks! :)
phobia
A: 

You could also use some kind of collection that stores cutoff values and descriptions. (I'm not a C# expert... maybe Dictionary<decimal,string>?) Iterate through it to find the last one that's less than your bmi, and return its corresponding label.

grossvogel
yeah this sounds like a nice solution ... you should be able to iterate over the key or value quite easily using a generic collection such as Dictionary. If you feel confident enough use a generic otherwise try using an enumerator like an enum and switch on the constant values that an enum variable currently holds.
IbrarMumtaz
+1  A: 

The switchstatement only supports integral types (enumerations are not listed but can be used with switch statements because they are backed by an integral type)(strings are also supported as pointed out by Changeling - see the comment for reference) and equality comparisons with constant values. Therefore you have to use some if statements.

if (bmi < 18.5M)
{
    bmi_description = "underweight.";
}
else if (bmi <= 25)
{
    // You missed the 'normal' case in your example.
}
else if (bmi <= 30)
{
    bmi_description = "overweight";
}
else if (bmi <= 40)
{
    bmi_description = "very overweight";
}
else
{
    bmi_description = "extreme overweight";
}

By the way your switch statement is a bit weired because you are switching from less than to greater than and using fall-through without breaks. I think one should use only one type of comparison to make the code easier to understand or reorder the checks and do not use fall-through.

if (bmi < 18.5M)
{
    bmi_description = "underweight.";
}
else if (bmi > 40)
{
    bmi_description = "extreme overweight";
}
else if (bmi > 30)
{
    bmi_description = "very overweight";
}
else if (bmi > 25)
{
    bmi_description = "overweight";
}
else
{
    // You missed the 'normal' case in your example.
}
Daniel Brückner
This is slightly inaccurate. Switch operator can also use strings.. see here: http://msdn.microsoft.com/en-us/library/06tc147t%28VS.71%29.aspx
0A0D
Thanks for pointing that out; updated the answer.
Daniel Brückner
@Daniel Brückner: Thanks for help! The M after the 18.5 decimal was what was missing from the other solutions, so this one solved it for me. Is there any "name" for what that M does, so that I can do some further reading on it?
phobia
As far as I know it has no special name besides simply suffix. See http://msdn.microsoft.com/en-us/library/aa691085.aspx and http://msdn.microsoft.com/en-us/library/364x0z75.aspx.
Daniel Brückner
A: 

The switch keyword works just fine with decimals. It's < and > that are giving you trouble.

Joel Coehoorn
Switch does *not* cooperate with decimal (besides this is new in 4.0).
Daniel Brückner
+1  A: 

Your further reading section,

switches can only be operated with on values or cases whereby the input value is a constant value that the switch can look up like an index and execute the attached code defined inside a case or case point or case label, whatever all can be used interchangeably.

Change y to to true in the first example and the switch should operate on 'b'.

The second example works because the second case is switching on a constant or 'const' value. Therefore you are meeting the basic criteria or what a switch needs. Although many on here would most definitely tell you not to code like this. Switch on a simple constant value and make sure that your switch accurately caters for each of the different values your supplied variable could be.

Try using an enum to make your code fall in line with standard .Net coding practises this comment also falls into line with making sure you dont pick up any bad habits if want to make a career of this???

REMEMBER: that you can use an enum and set it up to use decimal values as decimal is a value type so this meets the criteria of what an enum requires. As enum is defined in the .Net framework as a value type so only values types like number based types can be set-up to create an enum type in yuor custom code classes. Simply attach each value with a name or some sort as you have used above, like over weight and etc and make sure each entry in the enum has a logical ordering to it. i.e the entrys in terms of there decimal values have a clear cut definition of going up or down. Once your enum is setup, create a variable of the type of enum you just created and then supply this variable to your switch.

Have fun learning.

IbrarMumtaz
A: 

You could use the decimals as a string instead to get the desired result:

switch (s) {

    case "18.5" : // do something
    case "25" : // do something

    etc...

}

Reference: MSDN switch (C#)

0A0D