简体   繁体   中英

Case/Switch Statements in C#

I wanted to know if there's a way to declare a lot of case statements without having to write all of them. For example, my code:

            switch (Weight)
            {
                case 0 to 2;
                    ShippingCost = "3.69";

                case 3 to 4;
                    ShippingCost = "4.86";

                case 5 to 6;
                    ShippingCost = "5.63";

                case 7 to 8;
                    ShippingCost = "5.98";

                case 9 to 10;
                    ShippingCost = "6.28";

                case 11 to 30;
                    ShippingCost = "15.72";

            }

I started converting VB to C# and realized that in order to have more than one case statements you have to declare them. As you can see I have 11 to 30 and do not wanna have all those lines.

You cannot use comparisons in C# as you can in VB. You can however use fall-through cases, like so:

case 0:
case 1:
case 2:
  ShippingCost = "3.69";
  break;

case 3:
case 4:
  ShippingCost = "4.86";
  break;

Note that non-empty cases require either a throw , return or, break statement. Also note that you can only fall-through on empty cases.

Edit:

For completeness, as others have pointed out, it is probably more sensible in this case to use a series of if statements, like so:

if(Weight<=2) {
  ShippingCost = "3.69";
}
else if(Weight <= 4) {
  ShippingCost = "4.86";
}
... etc

There is direct equivalent in C#, however, you can use fall-through so you don't have to repeat the implementation:

switch (Weight)
{
    case 0:
    case 1:
    case 2:
       ShippingCost = "3.69";
       break;
       ...

If statements would probably suit you much better in this scenario:

if(Weight >= 0 && Weight <= 2){
    ShippingCost = "3.69";
} else if(Weight >= 3 && Weight <= 4){
    ShippingCost = "4.86";
}
...

Try like this: This solutions removes the need to write && in your else if statements

if(Weight >= 11 && Weight <= 30)
{
   ShippingCost = "15.72";
}
else if(Weight >= 9)
{
   ShippingCost = "6.28";
}
else if(Weight >= 7)
{
   ShippingCost = "5.98";
}
else if(Weight >= 5)
{
   ShippingCost = "5.63";
}
else if(Weight >= 3)
{
   ShippingCost = "4.86";
}
else if(Weight >= 0)
{
   ShippingCost = "3.69";
}

You could also write it as a Linq one liner:

var ShippingCost = (new[] { new { w = 2, p = "3,69" }, 
                            new { w = 4, p = "4.86" }, 
                            new { w = 6, p = "5.63" }, 
                            new { w = 8, p = "5.98" }, 
                            new { w = 10, p = "6.28" }, 
                            new { w = 30, p = "15.72" }})
             .First(x => Weight <= x.w).p;

You would, as others have already stated, want to make sure that shipping for items weighing more than 30 is also handled correctly.

How bout just using this sort of approach

private static double GetShippingCost(double weight)
{
    if (weight > 30) throw new ArgumentException("Weight over allowed maximum", "weight");

    if (weight <= 2) return 3.69;
    if (weight <= 4) return 4.86;
    if (weight <= 6) return 5.63;
    if (weight <= 8) return 5.98;
    if (weight <= 10) return 6.28;
    if (weight <= 30) return 15.72;

}

An alternative to using a case would be to write some kind of class to do the mapping, for example:

public sealed class CostsPerWeight
{
    class CostPerWeight
    {
        public int Low;
        public int High;
        public double Cost;
    }

    readonly List<CostPerWeight> costs = new List<CostPerWeight>();

    public CostsPerWeight Add(int low, int high, double result)
    {
        // Error handling omitted for brevity. 
        // Real code should check that low < high and that ranges do not overlap.

        costs.Add(new CostPerWeight { Low = low, High = high, Cost = result } );
        return this;
    }

    public double Cost(int weight)
    {
        // This throws if the weight isn't in the list.
        // If that's not what you want, you'd have to add extra error handling here.
        return costs.First(x => x.Low <= weight && weight <= x.High).Cost;
    }
}

Which you would use like this (I've used doubles instead of strings for the costs for this example, but you can use whatever type you need):

var costs = new CostsPerWeight()
    .Add( 0,  2,  3.69)
    .Add( 3,  4,  4.86)
    .Add( 5,  6,  5.63)
    .Add( 7,  8,  5.98)
    .Add( 9, 10,  6.28)
    .Add(11, 30, 15.72);

double shippingCost = costs.Cost(weight);

If you have a lot of these switch statements in VB, it would be worth considering this approach.

(The advantage of using this instead of a Linq one-liner is simply that it's easier to document and unit test. You could also create a CostsPerWeight class instance and pass it around - useful for decoupling code, dependency-injection and for unit testing.)

It does seem to me that the concept of looking up a cost based on a weight is crying out to be encapsulated in a class, rather than embedded piecemeal in various parts of the code.

Here's a more extended example of CostsPerWeight with more error handling:

public class CostsPerWeight
{
    class CostPerWeight
    {
        public int Low;
        public int High;
        public double Cost;
    }

    readonly List<CostPerWeight> costs = new List<CostPerWeight>();

    double min = double.MaxValue;
    double max = double.MinValue;
    double costForMin;

    public CostsPerWeight Add(int low, int high, double cost)
    {
        if (low > high)
            throw new ArgumentException(nameof(low) + " must be less than " + nameof(high));

        if (cost < 0)
            throw new ArgumentOutOfRangeException(nameof(cost), "cost must be greater than zero");

        costs.Add(new CostPerWeight { Low = low, High = high, Cost = cost } );

        if (low < min)
        {
            min = low;
            costForMin = cost;
        }

        if (high > max)
            max = high;

        return this;
    }

    public double Cost(int weight)
    {
        if (weight < min)
            return costForMin;

        if (weight > max)
            throw new InvalidOperationException($"Weight {weight} is out of range: Must be <= {max}");

        return costs.First(x => x.Low <= weight && weight <= x.High).Cost;
    }
}

The clearest way is to put your data into objects instead.

  private struct ShippingCost
  {
      public int MinWeight;
      public int MaxWeight;
      public decimal Cost;

      public ShippingCost(int min, int max, decimal cost)
      {
         MinWeight = min;
         MaxWeight = max;
         Cost = cost;
      }
  }

  private List<ShippingCost> Costs = new List<ShippingCost>
  {
      new ShippingCost(0, 2, 3.69m),
      new ShippingCost(3, 4, 4.86m),
      new ShippingCost(5, 6, 5.63m),
      new ShippingCost(7, 8, 5.98m),
      new ShippingCost(9, 10, 6.28m),
      new ShippingCost(11, 30, 15.72m),
  };

  // Choose shipping cost
  public decimal CalcShippingCost(int weight)
  {
      foreach (ShippingCost sc in Costs)
      {
          if (weight >= sc.MinWeight && weight <= sc.MaxWeight)
              return sc.Cost;
      }

      return 0.00m;     // default cost
  }

You can't do that in C#. The best option if your max value for weight is 30 is to the use the default case.

Otherwise, if you don't want something like

case 11:
case 12:
case 13:
....
case 28:
case 29:
case 30:

an "oldschool" if/else if will be the the most readable solution

I'd recommend storing the data in a container and iterating through the container. You could create your own class or use a .net class like Tuple:

var shippingCostsByWeight = new List<Tuple<int, int, string>>
{
    new Tuple<int, int, string>(0, 2, "3.69"),
    new Tuple<int, int, string>(3, 4, "4.86"),
    new Tuple<int, int, string>(5, 6, "5.63"),
    new Tuple<int, int, string>(7, 8, "5.98"),
    new Tuple<int, int, string>(9, 10, "6.28"),
    new Tuple<int, int, string>(11, 30, "15.72"),
};

ShippingCost = shippingCostsByWeight
                .First(tuple => weight >= tuple.Item1 && weight <= tuple.Item2).Item3;

You could use a fall-through case switch statement, which goes as the following:

case 0:
case 1:
case 2:
  shippingCost = "3.69";
  break;

... and so on

Which will cause 0 to set shippingCost to 3.69 aswell as 1 and 2. :)
That would be my Solution to this

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM