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

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

Subscribe on YouTube