Author avatar

Dániel Szabó

Harnessing the Power of Collection Initializers

Dániel Szabó

  • Jul 19, 2019
  • 8 Min read
  • 686 Views
  • Jul 19, 2019
  • 8 Min read
  • 686 Views
Languages Frameworks and Tools
C#

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:

  1. Windows
  2. Linux

The Function of a server can be:

  1. Web Server
  2. Application Server
  3. Management Server

We are using a three-tier approach:

  1. Development (developer playground)
  2. Testing (mirror image of the production)
  3. Production (the money maker)

Creating Our Class

We are going to create a very simply class, like this:

1public class Server
2{	
3	public string Name {get; set; }
4	public string OperatingSystem {get; set;}
5	public string Tier {get; set;}
6	public string Function {get; set;}
7}
csharp

As aforementioned, this class has four accessible properties. We can easily get and set these properties, as follows.

Setting the name:

1WebServer = new Server();
2WebServer.Name = "GateKeeper";
csharp

Printing the name to the console:

1Console.Writeline($"The server is called: {WebServer.Name}")
csharp

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.

1List<Server> servers = new List<Server>();
csharp

Now we can create our unique server instances.

Let's create a Web Server:

1WebServer = new Server();
2WebServer.Name = "GateKeeper";
3WebServer.OperatingSystem = "Windows";
4WebServer.Tier = "Production";
5WebServer.Function = "WebServer";
csharp

We need an Application Server too.

1AppServer = new Server();
2AppServer.Name = "HardWorker";
3AppServer.OperatingSystem = "Linux";
4AppServer.Tier = "Testing";
5AppServer.Function = "AppLicationServer";
csharp

Finally, our Management Server:

1MgmtServer = new Server();
2MgmtServer.Name = "TheBoss";
3MgmtServer.OperatingSystem = "Linux";
4MgmtServer.Tier = "Production";
5MgmtServer.Function = "ManagementServer";
csharp

Ugh... I'm getting tired of all this typing and we did not even add these to the list... let's do it.

1servers.Add(WebServer);
2servers.Add(AppServer);
3servers.Add(MgmtServer);
csharp

Let's see what we have here.

1foreach (Server machine in servers) {
2	Console.WriteLine($"The server called: {machine.Name} runs on {machine.OperatingSystem } inside Tier: {machine.Tier} and function: {machine.Function}");
3            }
csharp

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:

1using System.Collections.Generic;
csharp

Now, we have a list to create.

1List<Server> srvrs = new List<Server>
2{
3	new Server { Name = "Cell", Function = "ApplicationServer", Tier = "Production", OperatingSystem="Linux" },
4	new Server { Name = "TVirus", Function = "WebServer", Tier = "Test", OperatingSystem="Windows" },
5	new Server { Name = "Vendetta", Function = "ManagementServer", Tier = "Test", OperatingSystem="Linux" }
6};
csharp

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.

1using System;
2using System.Collections.Generic;
3
4namespace ConsoleApp1
5{
6    public class Server
7    {
8        public string Name { get; set; }
9        public string OperatingSystem { get; set; }
10        public string Tier { get; set; }
11        public string Function { get; set; }
12    }
13    class Program
14    {
15        // These references are necessary to be able to instantiate the class.
16        public static Server WebServer { get; set; }
17        public static Server AppServer { get; set; }
18        public static Server MgmtServer { get; set; }
19        static void Main(string[] args)
20        {
21            //Without the sugar
22            List<Server> servers = new List<Server>();
23
24            WebServer = new Server();
25            WebServer.Name = "GateKeeper";
26            WebServer.OperatingSystem = "Windows";
27            WebServer.Tier = "Production";
28            WebServer.Function = "WebServer";
29
30            AppServer = new Server();
31            AppServer.Name = "HardWorker";
32            AppServer.OperatingSystem = "Linux";
33            AppServer.Tier = "Testing";
34            AppServer.Function = "AppLicationServer";
35
36            MgmtServer = new Server();
37            MgmtServer.Name = "TheBoss";
38            MgmtServer.OperatingSystem = "Linux";
39            MgmtServer.Tier = "Production";
40            MgmtServer.Function = "ManagementServer";
41
42            servers.Add(WebServer);
43            servers.Add(AppServer);
44            servers.Add(MgmtServer);
45
46            foreach (Server machine in servers)
47            {
48                Console.WriteLine($"The server called: {machine.Name} runs on {machine.OperatingSystem } inside Tier: {machine.Tier} and function: {machine.Function}");
49            }
50            // The Syntactic sugar part.
51            List<Server> srvrs = new List<Server>
52            {
53                new Server { Name = "Cell", Function = "ApplicationServer", Tier = "Production", OperatingSystem="Linux" },
54                new Server { Name = "TVirus", Function = "WebServer", Tier = "Test", OperatingSystem="Windows" },
55                new Server { Name = "Vendetta", Function = "ManagementServer", Tier = "Test", OperatingSystem="Linux" }
56            };
57
58            foreach (Server machine in srvrs) {
59                Console.WriteLine($"The server called: {machine.Name} runs on {machine.OperatingSystem } inside Tier: {machine.Tier} and function: {machine.Function}");
60            }
61
62            Console.Read();
63        }
64    }
65}
csharp

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.