Code Focused

Cool Case Clauses in Visual Basic and C#

Each language treats case clauses differently, but there's a way you can make them less boring in C#.

Case clauses in Visual Basic are cool. You can load them up with all kinds of expressions and method calls, allowing you to fine-tune your criteria:

Select Case patrons
  Case 1, 2
    ' ----- Small table: based on item list
  Case 3 To 6
    ' ----- Medium table: based on range
  Case ReturnSeven()
    ' ----- Large table: based on method call
  Case Is > 7
    ' ----- Extra large table: based on comparison
End Select

In C#, things aren't so cool. Each case clause is limited to a single constant value; no expressions, no methods, no comparison operators. If you want to process complex expressions like those in Visual Basic, you would normally use a series of if statements. You could retain the case clauses, but only by first processing the test value through some if-statement equivalent:

string testValue =
  (new int[] { 1, 2 }).Contains(patrons) ? "S" :
  patrons >= 3 & patrons <= 6            ? "M" :
  patrons == ReturnSeven()               ? "L" :
  patrons > 7                            ? "XL" :
  "";

switch (testValue)
{
  case "S":
    // ----- Small table code here.
    break;
  case "M":
    // ----- Medium table code here.
    break;
  case "L":
    // ----- Large table code here.
    break;
  case "XL":
    // ----- Extra large table code here.
    break;
}

But what if you wanted to be as cool as those Select Case users? Can you combine boring C# switch statements with conditional processing statements that are as feature-rich as those in Visual Basic? Perhaps by combining generics, lambda expressions and LINQ? Why, yes, yes you can. Let's generate the test variable for the switch statement by using LINQ to process a collection of conditional lambda expressions.

In this make-believe restaurant example, the goal has been to determine how large of a table to provide to a group of hungry patrons. To determine that, you need criteria functions that each accepts a customer count and indicates, true or false, whether the criteria for that table size is met. Let's create a delegate -- a method template -- that will help provide consistency in all criteria functions:

private delegate bool TableCountMatch(int eaters);

Next, you need a place to store all of these criteria functions, linking them to a target table size. You could build a custom generic class that tracks criteria functions and table sizes in tandem. But lucky for you, there's already a collection type that includes room for two values -- a dictionary:

Dictionary<TableCountMatch, string> testCases =
  new Dictionary<TableCountMatch, string>();

Dictionary collections require that the "key" part of the dictionary be unique. Because each criteria function will be its own unique analysis system, you'll use that for the unique key, storing the associated table size as the dictionary value:

testCases.Add((eaters) => (new int[] { 1, 2 }).Contains(eaters), "S");
testCases.Add((eaters) => eaters >= 3 & eaters <= 6,             "M");
testCases.Add((eaters) => eaters == ReturnSeven(),               "L");
testCases.Add((eaters) => eaters > 7,                            "XL");

Now you have a collection of criteria functions, stored as delegates, and matching table sizes that get spit out when a function returns true for a specific customer count. You just need the tool that does the spitting, and LINQ is good for that:

string testValue =
  (from scanTest in testCases
  where scanTest.Key(patrons)
  select scanTest.Value).First();

switch (testValue)
{
  case "S":
    // ----- And so on...

The query processes each entry in the dictionary, passing the patron count to the "key" function delegate, and returning the table size "value" when a function returns true. In case multiple criteria functions match the customer count, it's a good idea to wrap the query in an extension method that returns just the first match.

This code isn't perfect. Because the LINQ query checks all entries in the criteria dictionary, each entry will evaluate, even when an earlier criteria returns true. Allowing every criteria function to evaluate might have undesirable side effects. And yet, it's cool, and what's programming about if you can't enjoy a little cool from time to time?

About the Author

Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.

comments powered by Disqus

Featured

  • Edit Local Images/Text from a .NET MAUI Blazor Hybrid App

    With .NET 6 and the latest Visual Studio 2022 preview, developers can create a hybrid Blazor/.NET MAUI app that can work with local machine resources in ways that ordinary Blazor (web) apps can't.

  • In VS 2022, WinForms Designer Still Chasing Parity with .NET Framework Version

    Microsoft provided an update on its years-long effort to bring the new Windows Forms designer up to speed with the old .NET Framework version.

  • See What's New for Git in Latest Visual Studio 2022 Update

    Four new Git features have been added to Visual Studio 2022 in the latest update, Preview 2, including the ability to compare branches and multi-repo branching.

  • Infragistics Adds 17 Controls to Blazor/Web Components Libraries

    Infragistics Ultimate 21.2 is out with an integrated low-code App Builder and 17 new controls for the Blazor and Web Components libraries of Ignite UI, the company's web-based UI toolkit.

Upcoming Events