With Version 6 of C#, a long-awaited feature called expression-bodied members was added to the programming language. The idea behind this feature is to make code more readable and concise.
We will see in this guide how this new feature can be incorporated into our code, clarify what members are, and see an example of using the concept on different member types. Using the example of an abstraction of a server found in data centers, we'll learn to use this new concept to create an easy-to-maintain class for our server.
The concept of members is related to object-oriented programming and its classes. Members allow you to modify behavior of your class, change how it interacts with other classes, and let it encapsulate information as needed. Members are the glue that keeps the application together and makes your code come alive.
The following members are available:
Expression-bodied members can be applied to any of the above, but consider which one is available in your current version of C#.
The members that are supported in version 6 include:
Supported in version 7:
The skeleton for the code to define expression-bodied members for your class looks like this:
1member => expression;
This small section will demonstrate how to create a constructor and a destructor for your class with expression body. Constructors are called when you create a new instance of your class, and they are used to initialize members. Destructors are also sometimes referred to as finalizers, and their goal is the opposite. Before the instance of the object dies due to the garbage collector's cleanup, it allows you to implement additional business logic or perform specific actions.
Define a class called Server
that has a single constructor that accepts a string argument, which will be the name of the server. The finalizer should emit the message "The end is near..."
With an expression-bodied constructor, it should look like this:
1using System;
2
3namespace Pluralsight
4{
5 public class Server
6 {
7 public string Name;
8 public Server(string name) => Name = name;
9 ~Server() => Console.WriteLine("The end is near...");
10 }
11 public class ConstructorBody
12 {
13 public static void Main()
14 {
15 Server a = new Server("Domain Controller");
16
17 Console.WriteLine($"The name of the server is: {a.Name}");
18 Console.ReadKey();
19 }
20 }
21}
The code's execution produces the following output:
1The name of the server is: Domain Controller
2The end is near...
Before expression-bodied constructors and finalizers became available, our code would have looked something like this:
1 public class Server
2 {
3 public string Name;
4
5 public Server(string name) {
6 Name = name;
7 }
8 ~Server()
9 {
10 Console.WriteLine("The end is near...");
11 }
12 }
It should be obvious which code is easier to read, though learning to use a new feature always comes with time and practice.
You may also refer to accessors as property getters and setters. This language element allows you to retrieve or modify a property of your class. They are important because they replace the old approach, which was to define GetProperty()
and SetProperty()
functions.
Transform the class and add two properties, vCPU
and RAM
.
1using System;
2
3namespace Pluralsight
4{
5 public class Server
6 {
7 public string Name;
8 private int _vCPU;
9 private int _RAM;
10 public int vCPU { get => _vCPU; set => _vCPU = value; }
11 public int RAM { get => _RAM; set => _RAM = value; }
12
13 public Server(string name) {
14 Name = name;
15 }
16 ~Server()
17 {
18 Console.WriteLine("The end is near...");
19 }
20 }
21 public class AccessorBody
22 {
23 public static void Main()
24 {
25 Server a = new Server("Domain Controller");
26 a.vCPU = 2;
27 a.RAM = 8;
28 Console.WriteLine($"The name of the server is: {a.Name}");
29 Console.WriteLine($"THe server has vCPU: {a.vCPU} and {a.RAM} GB of RAM!");
30
31 }
32 }
33}
You should see the following output after code execution:
1The name of the server is: Domain Controller
2THe server has vCPU: 2 and 8 GB of RAM!
3The end is near...
One difference you should notice is that _vCPU
and RAM
are defined as private properties of the class and are not directly accessible. Access to them is provided via the getter
and setter
accessors. This new notation makes it easier to understand the code and follow what happens with the property. When we refer to the actual members, they are called vCPU
and RAM
.
Let's say you would like to store information about which network adapters are present in your server. Indexers are a perfect solution for that. Just define a string array of five elements to store the network adapters, which in turn need an indexer with expression body to provide the specified values. The code should look something like this:
1using System;
2
3namespace Pluralsight
4{
5 public class Server
6 {
7 public string Name;
8 private int _vCPU;
9 private int _RAM;
10 public int vCPU { get => _vCPU; set => _vCPU = value; }
11 public int RAM { get => _RAM; set => _RAM = value; }
12
13 public string[] NetworkAdapters = new string[5];
14 public string this[int i] { get => NetworkAdapters[i]; set => NetworkAdapters[i] = value; }
15
16 public Server(string name) {
17 Name = name;
18 }
19 ~Server()
20 {
21 Console.WriteLine("The end is near...");
22 }
23 }
24 public class ConstructorBody
25 {
26
27 public static void Main()
28 {
29 Server a = new Server("Domain Controller");
30 a.vCPU = 2;
31 a.RAM = 8;
32 a.NetworkAdapters[0] = "LAN A";
33 a.NetworkAdapters[1] = "LAN B";
34 a.NetworkAdapters[2] = "LAN C";
35 Console.WriteLine($"The name of the server is: {a.Name}");
36 Console.WriteLine($"THe server has vCPU: {a.vCPU} and {a.RAM} GB of RAM!");
37 Console.WriteLine($"The following network adapters are available:");
38 foreach (string adapter in a.NetworkAdapters) {
39 if (!string.IsNullOrEmpty(adapter))
40 { Console.WriteLine($"\tNetwork Adapter: {adapter}"); }
41
42 };
43
44 }
45 }
46}
Upon execution, the following output is visible:
1The name of the server is: Domain Controller
2THe server has vCPU: 2 and 8 GB of RAM!
3The following network adapters are available:
4 Network Adapter: LAN A
5 Network Adapter: LAN B
6 Network Adapter: LAN C
7The end is near...
At the heart of the indexer in your class are the following two lines:
1public string[] NetworkAdapters = new string[5];
2public string this[int i] { get => NetworkAdapters[i]; set => NetworkAdapters[i] = value; }
The first one is simply a string array, and the second is the indexer itself with expression body.
When you assign network adapters, you are simply taking a value and passing it to the appropriate index of the adapter, which is a.NetworkAdapters[]
. If you retrieve the values, the foreach loop is your savior. It iterates over the array, and if it is not empty or null, the name of the adapter becomes visible. You could also store more information or use a more appropriate data structure to capture IP Address or MAC address.
Finally, you may want to add some extra functionality to your server class and define some expression-bodied methods, or as they are sometimes referred to, functions.
Introduce three functions, Start()
, Stop()
, and Restart()
, and a new property called State
with its getter and setter.
The final code should resemble this:
1using System;
2
3namespace Pluralsight
4{
5 public class Server
6 {
7 public string Name;
8 private int _vCPU;
9 private int _RAM;
10 private string _State;
11 public int vCPU { get => _vCPU; set => _vCPU = value; }
12 public int RAM { get => _RAM; set => _RAM = value; }
13 public string State { get => _State; set => _State = value; }
14
15 public string[] NetworkAdapters = new string[5];
16 public string this[int i] { get => NetworkAdapters[i]; set => NetworkAdapters[i] = value; }
17
18 public void Start() { Console.WriteLine($"Starting Server {Name}"); _State = "Started"; }
19 public void Stop() { Console.WriteLine($"Stopping Server {Name}"); _State = "Stopped"; }
20 public void Restart() { Console.WriteLine($"Restarting Server {Name}"); Stop(); Start(); }
21
22 public Server(string name) {
23 Name = name;
24 }
25 ~Server()
26 {
27 Console.WriteLine("The end is near...");
28 }
29 }
30 public class ConstructorBody
31 {
32
33 public static void Main()
34 {
35 Server a = new Server("Domain Controller");
36 a.vCPU = 2;
37 a.RAM = 8;
38 a.NetworkAdapters[0] = "LAN A";
39 a.NetworkAdapters[1] = "LAN B";
40 a.NetworkAdapters[2] = "LAN C";
41 Console.WriteLine($"The name of the server is: {a.Name}");
42 Console.WriteLine($"THe server has vCPU: {a.vCPU} and {a.RAM} GB of RAM!");
43 Console.WriteLine($"The following network adapters are available:");
44 foreach (string adapter in a.NetworkAdapters) {
45 if (!string.IsNullOrEmpty(adapter))
46 { Console.WriteLine($"\tNetwork Adapter: {adapter}"); }
47
48 };
49 a.Start();
50 a.Stop();
51 a.Restart();
52
53 }
54 }
55}
Upon execution, the following output should appear:
1The name of the server is: Domain Controller
2THe server has vCPU: 2 and 8 GB of RAM!
3The following network adapters are available:
4 Network Adapter: LAN A
5 Network Adapter: LAN B
6 Network Adapter: LAN C
7Starting Server Domain Controller
8Stopping Server Domain Controller
9Restarting Server Domain Controller
10Stopping Server Domain Controller
11Starting Server Domain Controller
12The end is near...
Notice how the expression-bodied method definitions allow us to implement the desired functionality with clean code and easily understood structure. You can also embed multiple function calls in a single method to chain actions.
There are some rules you need to be aware of when you are using expression-bodied members in a class:
In this guide, you learned what expression-bodied members contribute to feature extension in the C# language. You started with the foundational knowledge of what members are and how they fit in the programming language. After that, you saw each type demonstrated with a practical implementation and arrived at the final solution to model a server in your infrastructure. I hope this has been informative to you and I would like to thank you for reading it.