Expression-bodied Function Syntax in C#
Apr 1, 2020 • 12 Minute Read
Introduction
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.
Members
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:
- Constructors
- Destructors
- Property getters and setters
- Methods
- Indexers
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:
- Property getter
- Methods
Supported in version 7:
- Constructor
- Destructor
- Property setter
- Indexers
The skeleton for the code to define expression-bodied members for your class looks like this:
member => expression;
Constructors and Destructors
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:
using System;
namespace Pluralsight
{
public class Server
{
public string Name;
public Server(string name) => Name = name;
~Server() => Console.WriteLine("The end is near...");
}
public class ConstructorBody
{
public static void Main()
{
Server a = new Server("Domain Controller");
Console.WriteLine($"The name of the server is: {a.Name}");
Console.ReadKey();
}
}
}
The code's execution produces the following output:
The name of the server is: Domain Controller
The end is near...
Before expression-bodied constructors and finalizers became available, our code would have looked something like this:
public class Server
{
public string Name;
public Server(string name) {
Name = name;
}
~Server()
{
Console.WriteLine("The end is near...");
}
}
It should be obvious which code is easier to read, though learning to use a new feature always comes with time and practice.
Accessors
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.
using System;
namespace Pluralsight
{
public class Server
{
public string Name;
private int _vCPU;
private int _RAM;
public int vCPU { get => _vCPU; set => _vCPU = value; }
public int RAM { get => _RAM; set => _RAM = value; }
public Server(string name) {
Name = name;
}
~Server()
{
Console.WriteLine("The end is near...");
}
}
public class AccessorBody
{
public static void Main()
{
Server a = new Server("Domain Controller");
a.vCPU = 2;
a.RAM = 8;
Console.WriteLine($"The name of the server is: {a.Name}");
Console.WriteLine($"THe server has vCPU: {a.vCPU} and {a.RAM} GB of RAM!");
}
}
}
You should see the following output after code execution:
The name of the server is: Domain Controller
THe server has vCPU: 2 and 8 GB of RAM!
The 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.
Indexers
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:
using System;
namespace Pluralsight
{
public class Server
{
public string Name;
private int _vCPU;
private int _RAM;
public int vCPU { get => _vCPU; set => _vCPU = value; }
public int RAM { get => _RAM; set => _RAM = value; }
public string[] NetworkAdapters = new string[5];
public string this[int i] { get => NetworkAdapters[i]; set => NetworkAdapters[i] = value; }
public Server(string name) {
Name = name;
}
~Server()
{
Console.WriteLine("The end is near...");
}
}
public class ConstructorBody
{
public static void Main()
{
Server a = new Server("Domain Controller");
a.vCPU = 2;
a.RAM = 8;
a.NetworkAdapters[0] = "LAN A";
a.NetworkAdapters[1] = "LAN B";
a.NetworkAdapters[2] = "LAN C";
Console.WriteLine($"The name of the server is: {a.Name}");
Console.WriteLine($"THe server has vCPU: {a.vCPU} and {a.RAM} GB of RAM!");
Console.WriteLine($"The following network adapters are available:");
foreach (string adapter in a.NetworkAdapters) {
if (!string.IsNullOrEmpty(adapter))
{ Console.WriteLine($"\tNetwork Adapter: {adapter}"); }
};
}
}
}
Upon execution, the following output is visible:
The name of the server is: Domain Controller
THe server has vCPU: 2 and 8 GB of RAM!
The following network adapters are available:
Network Adapter: LAN A
Network Adapter: LAN B
Network Adapter: LAN C
The end is near...
At the heart of the indexer in your class are the following two lines:
public string[] NetworkAdapters = new string[5];
public 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.
Methods
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:
using System;
namespace Pluralsight
{
public class Server
{
public string Name;
private int _vCPU;
private int _RAM;
private string _State;
public int vCPU { get => _vCPU; set => _vCPU = value; }
public int RAM { get => _RAM; set => _RAM = value; }
public string State { get => _State; set => _State = value; }
public string[] NetworkAdapters = new string[5];
public string this[int i] { get => NetworkAdapters[i]; set => NetworkAdapters[i] = value; }
public void Start() { Console.WriteLine($"Starting Server {Name}"); _State = "Started"; }
public void Stop() { Console.WriteLine($"Stopping Server {Name}"); _State = "Stopped"; }
public void Restart() { Console.WriteLine($"Restarting Server {Name}"); Stop(); Start(); }
public Server(string name) {
Name = name;
}
~Server()
{
Console.WriteLine("The end is near...");
}
}
public class ConstructorBody
{
public static void Main()
{
Server a = new Server("Domain Controller");
a.vCPU = 2;
a.RAM = 8;
a.NetworkAdapters[0] = "LAN A";
a.NetworkAdapters[1] = "LAN B";
a.NetworkAdapters[2] = "LAN C";
Console.WriteLine($"The name of the server is: {a.Name}");
Console.WriteLine($"THe server has vCPU: {a.vCPU} and {a.RAM} GB of RAM!");
Console.WriteLine($"The following network adapters are available:");
foreach (string adapter in a.NetworkAdapters) {
if (!string.IsNullOrEmpty(adapter))
{ Console.WriteLine($"\tNetwork Adapter: {adapter}"); }
};
a.Start();
a.Stop();
a.Restart();
}
}
}
Upon execution, the following output should appear:
The name of the server is: Domain Controller
THe server has vCPU: 2 and 8 GB of RAM!
The following network adapters are available:
Network Adapter: LAN A
Network Adapter: LAN B
Network Adapter: LAN C
Starting Server Domain Controller
Stopping Server Domain Controller
Restarting Server Domain Controller
Stopping Server Domain Controller
Starting Server Domain Controller
The 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.
Pros and Cons
There are some rules you need to be aware of when you are using expression-bodied members in a class:
- The visibility of these members can only be public, protected, internal, private, and protected internal.
- You are able to declare members virtual or abstract.
- Members are capable of overriding base class methods.
- You are able to define static members.
- Loops are not supported in these types of members, but LINQ is supported.
- You are not able to use conditional statements.
Conclusion
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.