Learning the Language Nuances of C#
Have you ever had one of those moments in which you’ve spent some time learning the ins and outs of a subject, you already feel comfortable, maybe even cocky, in your level of expertise - until some trivial question comes up, and you think to yourself, “hmm, that never occurred to me, weird...”?
Let me give an example. Say you move to a foreign country, where they don’t speak your primary language. You start to learn the language and grow comfortable with day-to-day communication. But even after five years of living there, once in a while you come across a situation in which you realize you simply don’t know the proper translation of something simple – like “shovel,” or “mashed potatoes,” or even “plastic bag”!
I know what you’re thinking – what does this have to do with coding? Well, coding languages are no different from any other language. Our team at Viventium has stumbled upon a number of those “hmm” moments in C# while working on our product. If you don’t believe this is possible, try looking at these C# Obscurities – and just watch your opinion change.
1. Let’s say you have the following line of code:
var x = 5.5;
There seems to be very little that can go wrong here, doesn’t it? The question is: can you make this line of code throw a compilation error? Note that in all code samples, we assume the latest .NET framework version is used. More specifically, how can you make the compiler say, “Cannot implicitly convert type ‘double’ to ‘int’”?
We can, with the help of using aliases. According to C# Reference, “The right side of a using alias directive must always be a fully-qualified type regardless of the using directives that come before it”. As for the left side, it appears that certain keywords (e.g. async, await, partial, yield, etc.) are permitted there, most likely due to backward compatibility issues. The var keyword is among them, so the statement
using var = System.Int32;
var x = 5.5f; // this is actually: int x = 5.5
is totally valid. This makes all var effectively of type int, meaning the code above won't compile, but it may be useful when refactoring.
As a side note, have you heard of the using static directive introduced in C# 6?
2. Can you make this compile:
var blogTestClass = new BlogTestClass { 1, "purple" };
By only Changing this class definition…
public class BlogTestClass : List<int>
{
}
Many people might not be aware of this, but Initializer is just syntactic sugar for calling a ForEach loop on the values and calling the correlating add method.
public class BlogTestClass : List<int>
{
string myString;
public void Add(string specialValue)
{
myString = specialValue;
}
}
So, to make this compile, you just add an add method for string, and c# will call it for you in the initializer.
What valid reason could you use this for? Perhaps, to automatically convert char’s to their value:
public class BlogTestClass : List<int>
{
public void Add(char value)
{
var val = (int)Char.GetNumericValue(value);
this.Add(val);
}
}
3. Let’s see what you know about structs! Given this struct:
public struct GotchaStruct
{
public int x;
public GotchaStruct(int bs = 10)
{
x = bs;
}
}
What will the x value be when initializing like so?
var foo = new GotchaStruct();
Did you say 10? Don’t worry, that’s a common mistake! But in fact, structs do not allow you to define parameter-less constructors – so when initializing without a value, the default constructor is always called, This means foo.x will be 0.
4. Given this enumeration:
public enum Animal
{
Bear = 0,
Pig = 1,
};
Will any of these compile?
A. Animal anim = 1;
B. Animal anim = 0;
C. Animal anim = (Animal)543;
The answer: B and C are both valid. 1 is invalid because there is no cast, but 0 is allowed for an implicit cast too, as this was implemented for clearing flags.
Casting to enum works even if values are not present in the definition
What’s the takeaway here? Always add defaults when using a switch case on an enum. Even if you are sure you’ve addressed all of the Named values for the enum, someone can still case a value to an enum `(Animal)543;` - and then you will get an unexpected exception.
5. Default Rounding: Given these three numbers, what is the sum of their total?
var dec1 = Math.Round(3.5);
var dec2 = Math.Round(4.5);
var dec3 = Math.Round(5.5);
Was 15 your guess? Don’t forget that C# uses banker’s rounding, a set of rules in which by default odd numbers round up and even numbers round down. Banker’s rounding helps minimize errors that could be caused from always rounding the same direction. In this scenario the answer would be 14.
Keep these obscurities in mind as you work on fully mastering the C# language! Then, next time you come across some bizarre code, you won’t be caught with your thumb in your pocket forgetting how to say “potato.”
References:
(Banker’s rounding) https://en.wikipedia.org/wiki/IEEE_754#Roundings_to_nearest
(Enum Casting to 0) https://stackoverflow.com/q/14950750/1938988
(Struct Constructors) https://msdn.microsoft.com/en-us/library/aa288208(v=vs.71).aspx
(Enumerable Initializers) https://stackoverflow.com/a/2495801/1938988
(using Alias) https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive
Great post! funny how you can work with something for so long and still be surpised :)
Great article! That's why these "Learn language X in 10 days" books never work. It may take 10 days to learn, but 10 more years to master.