Syntactical Sugar
Many of the new features in C# 6 come under the heading of Syntactical Sugar.
Syntactical sugar refers to a simplification of existing syntax making it easier to read and write.
In practice it’s mostly about lowering the noise level.
An unsweetened and contrived example:
MyLongwindedNameClass[] allMyStuff = GetMyStuff(); MyCustomHoldingPen filteredStuff = new MyCustomHoldingPen(); for (int i = 0; i < allMyStuff.Length; i++) { MyLongWindedNameClass currItem = allMyStuff[i]; if (!filteredStuff.HasValue(allMyStuff[i])) { if (MeetsSomeCriteria(currItem)) { filteredStuff.AddValue(currItem); } } } DoSomething(filteredStuff);
Applying some Syntactical Sugar by using the var keyword, LINQ, and method chaining AKA fluent syntax our example becomes:
var allMyStuff = GetMyStuff().ToList(); var filteredStuff = (from item in allMyStuff where MeetsSomeCriteria(item) select item) .ToList(); DoSomething(filteredStuff);
Our “sweetening” results in less overall code, in other words less code to manually compose.
And while there’s less code it hasn’t been at the expense of clarity. On the contrary, by substituting the var keyword to banish superfluous syntactical type declarations and replacing the old school iteration and assignment constructs with LINQ we’ve ended up with code which reads more easily and clearly states its intent.
Index Initializer
At first glance the new Index Initializer language feature in C# 6 appears to be limited to improving the consistency of certain expressions. Looking a bit closer there’s more to it. Let’s take a look.
We’ve long been able to use the following Collection Initializer in cases where a Collection is immediately populated after its construction:
enum USState {...} var AreaCodeUSState = new Dictionary<string, USState> { {"408", USState.California}, {"701", USState.NorthDakota}, ... };
Behind the scenes the compiler takes our Collection Initializer and emits code to first create a Dictionary<TKey, TValue> followed by .Add() for each item in the Collection Intializer list.
A Collection Initializer can only be used on classes implementing IEnumerable because the compiler relies on IEnumerable.Add() to assign the values in the intitialization list. Were we instead using List< Tuple<string, USStates>> instead of Dictionary<string, USStates> our example would remain almost uchanged.
In C# 6 we could instead use an Index Initializer:
enum USState {...} var AreaCodeUSState = new Dictionary<string, USState> { ["408"] = USState.California, ["701"] = USState.NorthDakota, ... };
“So what; it’s a bit prettier maybe?” Or something similar was my first thought, albeit a mistaken thought.
Our first clue that’s something’s truly different is Index Initializers unlike Collection Initializers does not compile to a set of IEnumerable.Add() calls.
It’s something different, something important. It’s not so much adding items rather its initializing values by specifying their Key.
In pCode
Collection Initializer
create a Dictionary<string, USState> add to new Dictionary the following items: "408" USState.California "701", USState.NorthDakota
Index Initializer
create a Dictionary<string, USState> then using AreaCodeUSState's default Indexed property set the Value of Key "408" to USState.California set the Value of Key "701" to USState.NorthDakota
To make the distinction clear let’s add one more item to each initializer using the Key “408” twice, first setting it to USState.Confusion the second time setting it to USState.California:
enum USState {...} var AreaCodeUSState = new Dictionary<string, USState> { { "408", USState.Confusion}, { "701", USState.NorthDakota }, { "408", USState.California}, ... }; Console.WriteLine( AreaCodeUSState.Where(x => x.Key == "408").FirstOrDefault().Value );
enum USState {...} var AreaCodeUSState = new Dictionary<string, USState> { ["408"] = USState.Confusion, ["701"] = USState.NorthDakota, ["408"] = USState.California, ... }; Console.WriteLine( AreaCodeUSState2.Where(x => x.Key == "408").FirstOrDefault().Value ); // output = California
Both compile and run, what’s different during execution?
At run-time the Collection Intializer will throw an ArgumentException when attempting to .Add() the second “408” item because the key “408” already exists. An artifact of how Dictionary is implemented has escaped the constructor leaving the object’s consumer with an exception to be handle and null reference.
At run-time the Index Initializer performs without issue. The Value for Key “408” is set to USState.Confusion, then the Value for Key “701”, then the Value of Key “408” to USState.California.
Any gotchas?
The most obvious place one would get tripped up using Index Initializers would be when using them to initialize objects where the Key is also used internally as an absolute ordinal index. A slight detour is needed to clarify some potentially ambiguity when referring to Index and Key.
You say Index, I say Key yet neither is a Tomato:
The terms Index and Key are often treated as interchangeable. In many cases the values of an item’s Index and it’s Key are equivalent values thereby further muddying any distinction. However, they are different concepts. An Index is a location whereas a Key is something that uniquely identifies some piece of information. Let’s use a book as a metaphor; books contain indexed information. One opens a book to find information on the programming language Malbolge, turns to the book’s index, finds the one -very rare- entry for Malbolge, takes note of the page number, then lastly turns to said page number to get their information. In this case “Malbolge” uniquely describes the wanted information, the Key, applying the Key to the book’s index (it’s default indexed property as it were) an index entry can be located for the information specified by the Key, the index entry will also have a page number, say 1729, indicating the information’s location, the information’s Index in this particular instance -printing- of the book. Taking our metaphor into the realm of silly let’s assume the book is about specific Natural Numbers with one page per number organized ordinally; page 1 is about the number 1, page 2 the number 2 etc. Our philosopical reader, having eaten at a certain restaurant in the remote fringes, doesn’t need to use the book’s index in his meaningful search, information about 42 is located on page 42 and off he goes. The metaphorical Index and Key have equivalent values.
Situations where Key and Index are conflated can generate side effects, in the case of Index Initializers unexpected ones.
List<int> provides a good basis for illustration.
// List<T> using Index Initializer. // For clarity we assume a series starting with one unlike true a Fibonacci series: 0, 1, 1, 2, 3, 5... var fibonaccis = new List<int> { [0] = 1, [1] = 2, [2] = 3, [3] = 5, [4] = 8, [5] = 13 }
We are initializing a List<int> using an Index Initializer in a not uncommon manner. It compiles and works as expected; we are getting away with this code due to a side effect.
Let’s try a slight variation that amplifies the side effect.
Assume we only need Fibonacci’s where n % 2 == 1 or 0 so we’ll not allocate space for the ones we don’t need.
// sparse List<T> using Index Initializer var oddFibonaccis = new List<int> { [0] = 1, [1] = 2, [3] = 5, [5] = 13 }
This is valid and it compiles. Valid it may well be, but it’s not sound. List<T> does not allow the assigment of an index beyond the its current size.
Attempting to assign the Value 5 to Key 3 causes the List<int> to throw an ArguementOutOfRangeException. When List<T>.Add(item) is called the List’s size increments by one reflecting the newly added item. Since C# Lists use zero-based indexes there’s no issue adding items to a List<T> in this manner.
Index Initializers don’t use .Add(), then instead use the Default Indexed property to set the Value for a Key.
Here’s what happens:
Key 0 (Value 1), which is stored at Index 0, is assigned the List’s size is one, when Key 1 (Value 2), which is stored at Index 1, is assigned the List’s size is 2, then when Key 3(Value 5), which should be stored at Index 4, is assigned the List’s size is only 3.
Progression through the initialization sequence.
Key | Value | Index | Size | |
---|---|---|---|---|
[0] = 1 |
0 | 1 | 0 | 0 |
[1] = 2 |
1 | 2 | 1 | 1 |
[2] = 3 |
2 | 3 | 2 | 2 |
[3] = 5 |
3 | 5 | 3 | 3 |
[4] = 8 |
4 | 8 | 4 | 4 |
[5] = 13 |
5 | 13 | 5 | 5 |
Key | Value | Index | Size | |
---|---|---|---|---|
[0] = 1 |
0 | 1 | 0 | 0 |
[1] = 2 |
1 | 2 | 1 | 1 |
[3] = 5 |
3 | 5 | 2 | 2 |
[5] = 13 |
5 | 13 | 3 | 3 |
Key provided by the Index Initializer
Index is the ordinal index to where the Value should be stored.
In this case good old Collection Initialization syntax should be used.
var evenFibonaccis = new List<int>() { 1, 3, 5, 13 };
Takeaway
When an object has a visible and settable indexer the use of Index Intializers allows the initialization of indexed properties using proper keys.
In such cases the use of Index Initializers clearly expresses the association of a Key to its related Value along with helping to avoid the tight coupling and potential side effects brought by the possible conflation of index and key.
The post Index Initializers – Day 1 – VS 2015 Series appeared first on Falafel Software Blog.