5 Uncommon C# Statements/Keywords
After digging around in C#, I found some interesting discoveries. In today's post, I go over these uncommon C# statements/keywords with some examples.
With C# 8.0 coming soon, I decided to dig into C# and look at some interesting statements and/or keywords developers have forgotten about or don't use anymore.
For today's post, I wanted to bring them to the surface and see if anyone still uses them.
While these statements/keywords are definitely rare, I can't remember the last time I've used them.
Oh, and just because I mention them here doesn't mean you can run rampant writing goto statements everywhere! ;-)
GOTO
Who doesn't remember Goto?
Goto sends the program's execution to a labeled piece of code (and no, not line numbers).
Goto statements can be applied to a switch statement or with a label.
Here is one example:
class SwitchTest { static void Main() { Console.WriteLine("Coffee sizes: 1=Small 2=Medium 3=Large");
Console.Write("Please enter your selection: "); string s = Console.ReadLine();
int n = int.Parse(s); int cost = 0;
switch (n) { case 1: cost += 25; break; case 2: cost += 25; goto case 1; case 3: cost += 50; goto case 1; default: Console.WriteLine("Invalid selection."); break; }
if (cost != 0) { Console.WriteLine("Please insert {0} cents.", cost); }
Console.WriteLine("Thank you for your business."); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } /* Sample Input: 2 Sample Output: Coffee sizes: 1=Small 2=Medium 3=Large Please enter your selection: 2 Please insert 50 cents. Thank you for your business. */
The GOTO statement has been around for a while...and not just in C#, but other various languages in general. For example, I remember writing BASIC code on my Commodore VIC-20 using GOTO statements.
Man, things have changed, but, honestly, I don't miss the GOTO statement.
Now if I see it in a program, I scratch my head and ask "why is it there?"
Checked/Unchecked
This was an interesting find.
The checked
block is meant to explicitly enable overflow checking for integral-type arithmetic operations and conversions where the unchecked
block is meant to suppress or prevent overflow-checking.
An example would look like this:
// Checked block. checked { int i3 = 2147483647 + ten; Console.WriteLine(i3); }
Personally, I've never gone past the limits of an integer type to even warrant a checked/unchecked block, but that doesn't mean it won't happen in the future.
When
Now THIS is a versatile little statement.
You can use this in two ways:
- In the case label of a switch statement
- In the catch statement of a a try..catch exception block
In the case of an exception, this example catches a number of Http Status Codes thanks to when
.
class Program { static void Main() { Console.WriteLine(MakeRequest().Result); }
public static async Task<string> MakeRequest() { var client = new System.Net.Http.HttpClient(); var streamTask = client.GetStringAsync("https://localHost:10000"); try { var responseText = await streamTask; return responseText; } catch (HttpRequestException e) when (e.Message.Contains("301")) { return "Site Moved"; } catch (HttpRequestException e) when (e.Message.Contains("404")) { return "Page Not Found"; } catch (HttpRequestException e) { return e.Message; } } }
With multiple errors in an HttpRequestException, you really don't know what error occurred. This is where the when
clause makes sense and saves space.
When it comes to a case statement, the when
clause makes your code even more readable.
private static void ShowShapeInfo(Object obj) { switch (obj) { case Shape shape when shape.Area == 0: Console.WriteLine($"The shape: {shape.GetType().Name} with no dimensions"); break; case Rectangle r when r.Area > 0: Console.WriteLine("Information about the rectangle:"); Console.WriteLine($" Dimensions: {r.Length} x {r.Width}"); Console.WriteLine($" Area: {r.Area}"); break; case Square sq when sq.Area > 0: Console.WriteLine("Information about the square:"); Console.WriteLine($" Length of a side: {sq.Side}"); Console.WriteLine($" Area: {sq.Area}"); break; case Shape shape: Console.WriteLine($"A {shape.GetType().Name} shape"); break; case null: Console.WriteLine($"The {nameof(obj)} variable is uninitialized."); break; default: Console.WriteLine($"The {nameof(obj)} variable does not represent a Shape."); break; } }
This gives you an expanded syntax to properly hit your case statements under the proper conditions.
Explicit/Implicit
Again, never knew these existed.
The explicit keyword is meant for declaring a user-defined type conversion operator that must be invoked with a cast.
The example is a better way to define it.
struct Digit { byte value; public Digit(byte value) { if (value > 9) { throw new ArgumentException(); } this.value = value; }
// Define explicit byte-to-Digit conversion operator: public static explicit operator Digit(byte b) { Digit d = new Digit(b); Console.WriteLine("conversion occurred"); return d; } }
class ExplicitTest { static void Main() { try { byte b = 3; Digit d = (Digit)b; // explicit conversion } catch (Exception e) { Console.WriteLine("{0} Exception caught.", e); } } } /* Output: conversion occurred */
The implicit keyword declares an implicit user-defined type conversion operator (again, an example may clear things up).
class Digit { public Digit(double d) { val = d; } public double val; // ...other members
// User-defined conversion from Digit to double public static implicit operator double(Digit d) { return d.val; }
// User-defined conversion from double to Digit public static implicit operator Digit(double d) { return new Digit(d); } }
class Program { static void Main(string[] args) { Digit dig = new Digit(7);
//This call invokes the implicit "double" operator double num = dig;
//This call invokes the implicit "Digit" operator Digit dig2 = 12;
Console.WriteLine("num = {0} dig2 = {1}", num, dig2.val); Console.ReadLine(); } }
Of course, these only work on operators and I ask you...when did you need the ability to code custom operators?
Yeah...I thought so. ;-)
Extern Alias
Let's say you have to reference two versions of assemblies that use the same fully-qualified type names with two or more versions of an assembly in the same app.
By using an external assembly alias, those namespaces from each assembly can be wrapped inside a root-level namespaces names by the alias where they can be used n the same file.
Based on their example, you have two grids in your application. One uses an old version (grid.dll) and one uses the new version (grid20.dll). When you compile your code, on one assembly, use the /r compiler flag:
/r:GridV1=grid.dll
and on the other assembly, use the /r flag to identify that grid.
/r:GridV2=grid20.dll
Once these are in place, you can access them through your code by adding the extern alias keyword:
extern alias GridV1
extern alias GridV2
This allows you to version your code, but still keep them separated from each other.
Conclusion
These five statements/keywords may be buried under a pile of C# code and not used as much, but C# continues to impress me with the amount of language changes it delivers to developers making it extremely flexible and competitive with the latest languages out there.
It makes me wonder what's coming next for C#.
Do you use any of these keywords or statements? Did I miss any rare gems in C#? Post your comments below and let's discuss!