Harnessing the Power of Collection Initializers
Collection Initializers are a big part of creating a code which is easier to understand and more robust.
Jul 19, 2019 • 8 Minute Read
Introduction
Let's entertain ourselves with the following situation: we are DevOps Engineers. Our task is to create a small application that will perform actions based on our server's Operating System, Function, and Tier. We do not want to over-complicate this example, but we want our code to be elegant and straight on point. Of course, we also would like to name our servers.
This can be achieved by a syntactic sugar called Collection Initializers that can be used against all types that implement the IEnumerable interface which include an Add method!
The Operating System can be:
- Windows
- Linux
The Function of a server can be:
- Web Server
- Application Server
- Management Server
We are using a three-tier approach:
- Development (developer playground)
- Testing (mirror image of the production)
- Production (the money maker)
Creating Our Class
We are going to create a very simply class, like this:
public class Server
{
public string Name {get; set; }
public string OperatingSystem {get; set;}
public string Tier {get; set;}
public string Function {get; set;}
}
As aforementioned, this class has four accessible properties. We can easily get and set these properties, as follows.
Setting the name:
WebServer = new Server();
WebServer.Name = "GateKeeper";
Printing the name to the console:
Console.Writeline($"The server is called: {WebServer.Name}")
This WriteLine statement uses the f-string notation, which I believe is the most convenient and easiest to understand. Note the $ sign before the double quote.
Initializing without Collection Initializers
First things first: we need a list to hold our servers and this list should hold elements of type Server. The list will only work if we are importing the System.Collections.Generic library.
List<Server> servers = new List<Server>();
Now we can create our unique server instances.
Let's create a Web Server:
WebServer = new Server();
WebServer.Name = "GateKeeper";
WebServer.OperatingSystem = "Windows";
WebServer.Tier = "Production";
WebServer.Function = "WebServer";
We need an Application Server too.
AppServer = new Server();
AppServer.Name = "HardWorker";
AppServer.OperatingSystem = "Linux";
AppServer.Tier = "Testing";
AppServer.Function = "AppLicationServer";
Finally, our Management Server:
MgmtServer = new Server();
MgmtServer.Name = "TheBoss";
MgmtServer.OperatingSystem = "Linux";
MgmtServer.Tier = "Production";
MgmtServer.Function = "ManagementServer";
Ugh... I'm getting tired of all this typing and we did not even add these to the list... let's do it.
servers.Add(WebServer);
servers.Add(AppServer);
servers.Add(MgmtServer);
Let's see what we have here.
foreach (Server machine in servers) {
Console.WriteLine($"The server called: {machine.Name} runs on {machine.OperatingSystem } inside Tier: {machine.Tier} and function: {machine.Function}");
}
The Problem with This Approach
This is not an easy to maintain application; servers come and go, they get renamed, and even their functions can change. This means we need a more flexible solution to create our list. One that does not require five new lines of code per server definition and one extra to add it to the list. There are too many steps where our fragile process can go wrong.
Syntactic Sugar to the Rescue
In order to use this approach, we need to add the following statement to the top of our app:
using System.Collections.Generic;
Now, we have a list to create.
List<Server> srvrs = new List<Server>
{
new Server { Name = "Cell", Function = "ApplicationServer", Tier = "Production", OperatingSystem="Linux" },
new Server { Name = "TVirus", Function = "WebServer", Tier = "Test", OperatingSystem="Windows" },
new Server { Name = "Vendetta", Function = "ManagementServer", Tier = "Test", OperatingSystem="Linux" }
};
Well, that was it. Now, every time we have a new server, we just give it another line, set the properties, and we are good to go. We could also further customize our app by simply pulling the list of servers with their properties from a web resource or a database. That way, we could iterate over the source and create our list dynamically. But that is a topic for another guide!
The Whole Application
This is the full source code, to make sure you don't miss any details. Note how I have created a separate class for the server and then used the public static in our Program class to reference it.
using System;
using System.Collections.Generic;
namespace ConsoleApp1
{
public class Server
{
public string Name { get; set; }
public string OperatingSystem { get; set; }
public string Tier { get; set; }
public string Function { get; set; }
}
class Program
{
// These references are necessary to be able to instantiate the class.
public static Server WebServer { get; set; }
public static Server AppServer { get; set; }
public static Server MgmtServer { get; set; }
static void Main(string[] args)
{
//Without the sugar
List<Server> servers = new List<Server>();
WebServer = new Server();
WebServer.Name = "GateKeeper";
WebServer.OperatingSystem = "Windows";
WebServer.Tier = "Production";
WebServer.Function = "WebServer";
AppServer = new Server();
AppServer.Name = "HardWorker";
AppServer.OperatingSystem = "Linux";
AppServer.Tier = "Testing";
AppServer.Function = "AppLicationServer";
MgmtServer = new Server();
MgmtServer.Name = "TheBoss";
MgmtServer.OperatingSystem = "Linux";
MgmtServer.Tier = "Production";
MgmtServer.Function = "ManagementServer";
servers.Add(WebServer);
servers.Add(AppServer);
servers.Add(MgmtServer);
foreach (Server machine in servers)
{
Console.WriteLine($"The server called: {machine.Name} runs on {machine.OperatingSystem } inside Tier: {machine.Tier} and function: {machine.Function}");
}
// The Syntactic sugar part.
List<Server> srvrs = new List<Server>
{
new Server { Name = "Cell", Function = "ApplicationServer", Tier = "Production", OperatingSystem="Linux" },
new Server { Name = "TVirus", Function = "WebServer", Tier = "Test", OperatingSystem="Windows" },
new Server { Name = "Vendetta", Function = "ManagementServer", Tier = "Test", OperatingSystem="Linux" }
};
foreach (Server machine in srvrs) {
Console.WriteLine($"The server called: {machine.Name} runs on {machine.OperatingSystem } inside Tier: {machine.Tier} and function: {machine.Function}");
}
Console.Read();
}
}
}
Conclusion
Although collection initializers are just syntactic-sugar, they play a great part in creating a code which is easier to understand and more robust in a sense that it protects the developer from mistakes by reducing the complexity of modification. You can see that, if we use this feature, in the above example,the code size is almost half of the original. These features come to a programing language every once in a while and they are a very precious tool in a developers toolset. It's worth the time to keep up-to-date with the development of a language and what kind of new features are introduced, from time to time.