As I was perusing the list of Visual Studio Featured Videos I spotted this little nugget and thought I’d share:
I’ve taken these features for a drive around the block; in addition to noting down some of my first impressions.
The philosophy outlined is one of small, incremental changes with the emphasis on cleaning up code and reducing boilerplate logic, as opposed to grand concepts that will take time to learn and implement. Sounding all fine and dandy to me 🙂
Here’s the round-up of features outlined…
Getter-Only Auto-Properties
Getter-Only Auto-Properties create a read-only backing field (on compilation) which can be assigned via the constructor or to the property directly as follows (which is consistent with how read-only fields function). Prior to this, it did definitely make it slightly more awkward to work with immutable types in relation to Auto-Properties. With this new syntax in play, the need to include things such as a ‘private set’, for these types, on an Auto-Property will be negated.
Assigning via the constructor:
/// <summary> /// Outlines the definition for /// a Rectangle object. /// </summary> public class Rectangle { #region Getter-Only Auto-Properties /// <summary> /// Allows access to the /// Rectangles Height. /// </summary> public int Height { get; } /// <summary> /// Allows access to the /// Rectangles Width. /// </summary> public int Width { get; } #endregion Getter-Only Auto-Properties #region Constructor /// <summary> /// Constructor that initialises /// this Rectangle. /// </summary> /// <param name="height">The height of the Rectangle to create.</param> /// <param name="width">The width of the Rectangle to create.</param> public Rectangle(int height, int width) { Height = height; Width = width; } #endregion Constructor }
Assigning to the property directly:
/// <summary> /// Allows access to the /// Rectangles Height. /// </summary> public int Height { get; } = 5; /// <summary> /// Allows access to the /// Rectangles Width. /// </summary> public int Width { get; } = 10;
It’s also worth stating here that the direct initialisation of properties works exactly the same way for classic Auto-Properties with getters and setters, so you’re all covered there.
The nameof Operator
When there is a requirement to use the name of a variable/parameter, as a string, directly in code the new ’nameof’ keyword allows you to reference something in a way that will be sensitive to any underlying changes to the variable/parameter name. For instance, if the name of the height/width parameters were altered in the constructor signature for our Rectangle object we can use Visual Studio to directly update the references to these variables within the code that throws exceptions. If these were referenced as strings they would not be updated:
#region Constructor /// <summary> /// Constructor that initialises /// this Rectangle. Exceptions use the nameof keyword /// to prevent issues whereby variable names change. /// </summary> /// <param name="height">The height of the Rectangle to create.</param> /// <param name="width">The width of the Rectangle to create.</param> public Rectangle(int height, int width) { //Ensure the height value provided falls within the specified limit if (height < minimumLengthAllowed || height > maximumLengthAllowed) { throw new ArgumentOutOfRangeException(nameof(height)); } //Ensure the width value provided falls within the specified limit if (width < minimumLengthAllowed || width > maximumLengthAllowed) { throw new ArgumentOutOfRangeException(nameof(width)); } //If height matches width then we would have a square! Ensure this is pushed back to the caller as an error if (height == width) { throw new ArgumentException("The parameters provided are of the same value (which will make a square!).", nameof(height)); } //Values provided are safe, initialise this object Height = height; Width = width; } #endregion Constructor
Using Static Members
This next example shows the use of the ‘static’ using clause that targets types instead of namespaces. It’s essentially a fast way to bring into scope static members of a type; this means that you no longer need to reference the type at all when using static members. The following example outlines some code for generating a random rectangle then, within the program class, static members of the Rectangle and Console class are used without needing to be fully qualified.
Outline of the functionality being called within the Rectangle type:
#region Private Constants //The minimum length (as an integer) for any 'side' of a Rectangle private const int minimumLengthAllowed = 1; //The maximum length (as an integer) for any 'side' of a Rectangle private const int maximumLengthAllowed = 20; #endregion Private Constants ........ #region Public Static Methods /// <summary> /// Public method that returns a Rectangle with a /// random height and width (within set parameters). /// </summary> /// <returns>A Rectangle with a random height and width between the allowed values.</returns> public static Rectangle GetRandomRectangle() { //Create an rng object and generate a random height/width Random rng = new Random(); int height = rng.Next(minimumLengthAllowed, maximumLengthAllowed + 1), width = rng.Next(minimumLengthAllowed, maximumLengthAllowed + 1); //Bit of protection here, increment/decrement the height just to prevent the Rectangle from being a Square (if height matches width) CleanRectangleValues(ref height, width); //Generate the new Rectangle and return it to the caller return new Rectangle(height, width); } #endregion Public Static Methods
Bringing static members of the Rectangle and Console types directly into scope:
using System; using CSharpSixTest.Shapes; //Bring the static members of the Rectangle and Console types directly into scope (meaning they don't need to be fully qualified) using static CSharpSixTest.Shapes.Rectangle; using static System.Console; namespace CSharpSixTest { /// <summary> /// Main Program Class. /// </summary> class Program { /// <summary> /// Main method. /// </summary> /// <param name="args">Optional input arguments</param> static void Main(string[] args) { //Create a new random Rectangle calling the static Rectangle class GetRandomRectangle method (notice that the type does not to be included here) Rectangle newRectangle = GetRandomRectangle(); //Use static members of System.Console without needed to fully qualify them (with type) WriteLine(newRectangle.Height); WriteLine(newRectangle.Width); ReadKey(); } } }
As with static members, the idea expressed here works exactly the same with enums. The features video (link above) that demonstrated this does bring up the interesting point that, if overused, this could cause code to be unnecessarily verbose. I’ll agree here; this is certainly something that I will use sparingly and only whereby the purpose of the member being accessed carries enough clarity on its own without the type prefix being required.
String Interpolation and Expression-Bodied Methods/Properties
I really love String.Format but I would certainly say I’ve tripped up once or twice when specifying larger expressions, whereby multiple arguments are being pushed into a resultant string. With String Interpolation syntax, we can now push values directly into a string, in a very terse and easy to understand way:
Before String Interpolation:
#region Rectangle ToString (old school) /// <summary> /// Old style ToString() method. /// </summary> /// <returns>A string representation for a Rectangle.</returns> public override string ToString() { return string.Format("Height = {0}; Width = {1};", Height, Width); } #endregion Rectangle ToString (old school)
After String Interpolation (as an Expression-Bodied method now):
/// <summary> /// Rectangle overriden ToString() method showing an example /// of an Expression-Bodied method using String Interpolation. /// </summary> /// <returns>A string representation for a Rectangle.</returns> public override string ToString() => $"Height = { Height }; Width = { Width };"; //Values embedded in statement - Very terse and easy to understand (less error prone)
In this previous example I coupled String Interpolation with a method using Expression-Bodied syntax. This utilises the existing lambda syntax so you can shorthand a method definition into a single line (for very simple method bodies that perform a single function or just exposes a single return statement for example).
The Expression-Bodied syntax can be applied to properties (get only). Notice that the get and return keywords are not required, these are just implied.
Old style Computed Property:
#region Old Style Computed Property /// <summary> /// Returns a value that represents the /// perimeter for this Rectangle. /// </summary> public int Perimeter { get { return (2 * (Height + Width)); } } #endregion Old Style Computed Property
Expression-Bodied Property:
#region Expression-Bodied Computed Property /// <summary> /// Returns a value that represents the /// perimeter for this Rectangle. /// </summary> public int Perimeter => (2 * (Height + Width)); #endregion Expression-Bodied Computed Property
Index Initialisers
In the past Index Initialisers could not be used with Object Initialisers; however, this is now possible (the below example shows how to break down what would have been four lines of code in a method body down to one, simple Expression-Bodied method).
Old method containing code setting up an object with Index Initialisers:
#region Public Methods /// <summary> /// Convert this Rectangle to a Json object. /// </summary> /// <returns>A Json object based on a particular Rectangle instance.</returns> public JObject ToJson() { JObject result = new JObject(); result["height"] = Height; result["width"] = Width; return result; } #endregion Public Methods
Index Initialisers can now be placed inside Object Initialisers:
#region Public Methods /// <summary> /// Convert this Rectangle to a Json object. /// </summary> /// <returns>A Json object based on a particular Rectangle instance.</returns> public JObject ToJson() { JObject result = new JObject() {["height"] = Height, ["width"] = Width }; return result; } #endregion Public Methods
Code cut down further into a single line, Expression-Bodied method:
#region Public Methods /// <summary> /// Convert this Rectangle to a Json object. /// </summary> /// <returns>A Json object based on a particular Rectangle instance.</returns> public JObject ToJson() => new JObject() {["height"] = Height, ["width"] = Width }; #endregion Public Methods
Null Conditional Operators
A new operator, dubbed the ‘Elvis Operator’, has been introduced to make short work of null checks (i.e. whereby multiple checks for null are required before calling properties on an object, for example). This is something I really like; the following example shows the reduction in code when using Null Conditional Operators (in additional to bringing in the new ‘using static’ clause so we don’t need to fully qualify enum values).
Code prior to any Null Conditional Operators being applied:
/// <summary> /// Public static helper method to convert Json /// into a Rectangle. /// </summary> /// <param name="json">The Json object to transform into a Rectangle.</param> /// <returns>A Rectangle object based on Json.</returns> public static Rectangle FromJson(JObject json) { //Check the transformation can actually be performed if (json != null && json["width"] != null && json["width"].Type == JTokenType.Integer && json["height"] != null && json["height"].Type == JTokenType.Integer) { //Get the height/width from the Json object int height = (int)json["height"], width = (int)json["width"]; //Tidy these values as appropriate CleanRectangleValues(ref height, width); //Return a new Rectangle as specified by the values in the Json object return new Rectangle(height, width); } return null; } ........ /// <summary> /// Private static helper method that increments/decrements /// the height value provided based on the width (if they match). /// Helps prevent making 'Squares'. /// </summary> /// <param name="height">A Rectangles prospective height value (passed by ref).</param> /// <param name="width">A Rectangles prospective width value.</param> private static void CleanRectangleValues(ref int height, int width) { //If height matches width see if we need to increment or decrement height (so we end up with values that make a valid Rectangle) if (height == width) { if (height != maximumLengthAllowed) { height++; } else { height--; } } }
Only perform the .Type check if the json object contains a height/width property:
/// <summary> /// Public static helper method to convert Json /// into a Rectangle. /// </summary> /// <param name="json">The Json object to transform into a Rectangle.</param> /// <returns>A Rectangle object based on Json.</returns> public static Rectangle FromJson(JObject json) { //Check the transformation can actually be performed if (json != null && json["width"]?.Type == JTokenType.Integer && json["height"]?.Type == JTokenType.Integer) { //Get the height/width from the Json object int height = (int)json["height"], width = (int)json["width"]; //Tidy these values as appropriate CleanRectangleValues(ref height, ref width); //Return a new Rectangle as specified by the values in the Json object return new Rectangle(height, width); } return null; }
Now only perform the .Type check if the json object contains a height/width property and is not null itself:
/// <summary> /// Public static helper method to convert Json /// into a Rectangle. /// </summary> /// <param name="json">The Json object to transform into a Rectangle.</param> /// <returns>A Rectangle object based on Json.</returns> public static Rectangle FromJson(JObject json) { //Check the transformation can actually be performed if (json?["width"]?.Type == JTokenType.Integer && json?["height"]?.Type == JTokenType.Integer) { //Get the height/width from the Json object int height = (int)json["height"], width = (int)json["width"]; //Tidy these values as appropriate CleanRectangleValues(ref height, ref width); //Return a new Rectangle as specified by the values in the Json object return new Rectangle(height, width); } return null; }
Adjusted code (‘using static’ within the using clause section) to reduce code further by not fully qualifying enum types:
/// <summary> /// Public static helper method to convert Json /// into a Rectangle. /// </summary> /// <param name="json">The Json object to transform into a Rectangle.</param> /// <returns>A Rectangle object based on Json.</returns> public static Rectangle FromJson(JObject json) { //Check the transformation can actually be performed if (json?["width"]?.Type == Integer && json?["height"]?.Type == Integer) { //Get the height/width from the Json object int height = (int)json["height"], width = (int)json["width"]; //Tidy these values as appropriate CleanRectangleValues(ref height, width); //Return a new Rectangle as specified by the values in the Json object return new Rectangle(height, width); } return null; }
This is so very, very cool. There are two ideas on display here; firstly that the ‘?’ can be placed to the right of a reference variable to state the intention that anything to the right of the operator should only be considered if the variable is not null (in this case we also would not fall inside the if statement). Next up, the ‘?’ is applied to say, ‘only check .Type == Integer (a Json JTokenType enum value) if the JObject contains a ‘height/width’ property. If this is null, do not call .Type’. I really like this and can imagine applying this a great deal to simplify code and leave the underlying intention in plain sight, for other developers to see.
Null Conditional Operators with Events
The Null Conditional Operator can also be used to trim down the level of boilerplate code that comes with triggering events in a thread-safe manner, as you can see from the below snippet (which is also using Expression-Bodied method syntax where possible):
#region Event Handling /// <summary> /// Outlines a method definition for subscribers to match /// (that has no return type). /// </summary> /// <param name="sender">This will be the Rectangle involved.</param> /// <param name="e">Any event arguments associated with the event.</param> public delegate void RectangleFlippedHandler(object sender, EventArgs e); /// <summary> /// Event for flipping a Rectangle (functionally does nothing, for /// illustration only). /// </summary> public event RectangleFlippedHandler RectangleFlipped; /// <summary> /// Public method that flips a Rectangle (just fires a vanilla event). /// </summary> public void FlipRectangle() => OnRectangleFlipped(EventArgs.Empty); /// <summary> /// Protected method that invokes the RectangleFlipped event /// and sends messages to all listeners (if RectangleFlipped is not null - i.e. /// they are subscribers - Using the Elvis operator!). /// </summary> /// <param name="e">Passed in Event Arguments.</param> protected void OnRectangleFlipped(EventArgs e) => RectangleFlipped?.Invoke(this, e); #endregion Event Handling
Exception Filters and Await Changes
Exception Filters can now be applied in C# allowing the developer specific control over when exceptions are caught (or thrown back to the caller). In addition, the ‘await’ keyword can now be used with catch and finally blocks, allowing asynchronous tasks to be kicked off within these scopes:
#region Exception Handling Changes Example /// <summary> /// Public method that will cause an exception. /// </summary> public async void ExceptionMethod() { try { //BANG! CallExceptionThrowingMethod(); } catch (InvalidOperationException ex) when (ex.InnerException != null) //Only catch here if ex.InnerException is not null, otherwise throw { //Actually using System.Diagnostics.Debug.WriteLine - Brought in with 'using static' WriteLine(ex.Message); await LogRectangleMessagesAsync(ex.Message); } finally { WriteLine("Finished processing within the ExceptionMethod"); //Await can be used here also!!! } } /// <summary> /// Logs a message to the console after a short delay. /// </summary> /// <param name="logMessage">The message to log.</param> /// <returns>No result.</returns> private async Task LogRectangleMessagesAsync(string logMessage) { await Task.Delay(3000); Console.WriteLine(string.Concat("Logged: ", logMessage)); } /// <summary> /// Method that simply throws exceptions. /// </summary> private void CallExceptionThrowingMethod() { //Change the exception thrown here to see exception filtering in action within the ExceptionMethod method //throw new InvalidOperationException("A configuration exception occurred!"); throw new InvalidOperationException("An invalid operation was performed!", new InvalidCastException("An invalid cast was performed!")); } #endregion Exception Handling Changes Example
Lastly, for reference here are the complete Program and Rectangle class definitions:
using System; using Newtonsoft.Json.Linq; using System.Threading.Tasks; using static Newtonsoft.Json.Linq.JTokenType; using static System.Diagnostics.Debug; namespace CSharpSixTest.Shapes { /// <summary> /// Outlines the definition for /// a Rectangle object. /// </summary> public class Rectangle { #region Private Constants //The minimum length (as an integer) for any 'side' of a Rectangle private const int minimumLengthAllowed = 1; //The maximum length (as an integer) for any 'side' of a Rectangle private const int maximumLengthAllowed = 20; #endregion Private Constants #region Getter-Only Auto-Properties /// <summary> /// Allows access to the /// Rectangles Height. /// </summary> public int Height { get; } /// <summary> /// Allows access to the /// Rectangles Width. /// </summary> public int Width { get; } #endregion Getter-Only Auto-Properties #region Expression-Bodied Property /// <summary> /// Returns a value that represents the /// perimeter for this Rectangle. /// </summary> public int Perimeter => (2 * (Height + Width)); #endregion Expression-Bodied Property #region Constructor /// <summary> /// Constructor that initialises /// this Rectangle. Exceptions use the nameof keyword /// to prevent issues whereby variable names change. /// </summary> /// <param name="height">The height of the Rectangle to create.</param> /// <param name="width">The width of the Rectangle to create.</param> public Rectangle(int height, int width) { //Ensure the height value provided falls within the specified limit if (height < minimumLengthAllowed || height > maximumLengthAllowed) { throw new ArgumentOutOfRangeException(nameof(height)); } //Ensure the width value provided falls within the specified limit if (width < minimumLengthAllowed || width > maximumLengthAllowed) { throw new ArgumentOutOfRangeException(nameof(width)); } //If height matches width then we would have a square! Ensure this is pushed back to the caller as an error if (height == width) { throw new ArgumentException("The parameters provided are of the same value (which will make a square!).", nameof(height)); } //Values provided are safe, initialise this object Height = height; Width = width; } #endregion Constructor #region Expression-Bodied/String Interpolation Overriden Methods /// <summary> /// Rectangle overriden ToString() method showing an example /// of an Expression-Bodied method using String Interpolation. /// </summary> /// <returns>A string representation for a Rectangle.</returns> public override string ToString() => $"Height = { Height }; Width = { Width };"; //Values embedded in statement - Very terse and easy to understand (less error prone) #endregion Expression-Bodied/String Interpolation Overriden Methods #region Public Methods /// <summary> /// Convert this Rectangle to a Json object. /// </summary> /// <returns>A Json object based on a particular Rectangle instance.</returns> public JObject ToJson() => new JObject() {["height"] = Height, ["width"] = Width }; #endregion Public Methods #region Public Static Methods /// <summary> /// Public method that returns a Rectangle with a /// random height and width (within set parameters). /// </summary> /// <returns>A Rectangle with a random height and width between the allowed values.</returns> public static Rectangle GetRandomRectangle() { //Create an rng object and generate a random height/width Random rng = new Random(); int height = rng.Next(minimumLengthAllowed, maximumLengthAllowed + 1), width = rng.Next(minimumLengthAllowed, maximumLengthAllowed + 1); //Bit of protection here, increment/decrement the height just to prevent the Rectangle from being a Square (if height matches width) CleanRectangleValues(ref height, width); //Generate the new Rectangle and return it to the caller return new Rectangle(height, width); } /// <summary> /// Public static helper method to convert Json /// into a Rectangle. /// </summary> /// <param name="json">The Json object to transform into a Rectangle.</param> /// <returns>A Rectangle object based on Json.</returns> public static Rectangle FromJson(JObject json) { //Check the transformation can actually be performed if (json?["width"]?.Type == Integer && json?["height"]?.Type == Integer) { //Get the height/width from the Json object int height = (int)json["height"], width = (int)json["width"]; //Tidy these values as appropriate CleanRectangleValues(ref height, width); //Return a new Rectangle as specified by the values in the Json object return new Rectangle(height, width); } return null; } #endregion Public Static Methods #region Private Static Helpers /// <summary> /// Private static helper method that increments/decrements /// the height value provided based on the width (if they match). /// Helps prevent making 'Squares'. /// </summary> /// <param name="height">A Rectangles prospective height value (passed by ref).</param> /// <param name="width">A Rectangles prospective width value.</param> private static void CleanRectangleValues(ref int height, int width) { //If height matches width see if we need to increment or decrement height (so we end up with values that make a valid Rectangle) if (height == width) { if (height != maximumLengthAllowed) { height++; } else { height--; } } } #endregion Private Static Helpers #region Event Handling /// <summary> /// Outlines a method definition for subscribers to match /// (that has no return type). /// </summary> /// <param name="sender">This will be the Rectangle involved.</param> /// <param name="e">Any event arguments associated with the event.</param> public delegate void RectangleFlippedHandler(object sender, EventArgs e); /// <summary> /// Event for flipping a Rectangle (functionally does nothing, for /// illustration only). /// </summary> public event RectangleFlippedHandler RectangleFlipped; /// <summary> /// Public method that flips a Rectangle (just fires a vanilla event). /// </summary> public void FlipRectangle() => OnRectangleFlipped(EventArgs.Empty); /// <summary> /// Protected method that invokes the RectangleFlipped event /// and sends messages to all listeners (if RectangleFlipped is not null - i.e. /// they are subscribers - Using the Elvis operator!). /// </summary> /// <param name="e">Passed in Event Arguments.</param> protected void OnRectangleFlipped(EventArgs e) => RectangleFlipped?.Invoke(this, e); #endregion Event Handling #region Exception Handling Changes Example /// <summary> /// Public method that will cause an exception. /// </summary> public async void ExceptionMethod() { try { //BANG! CallExceptionThrowingMethod(); } catch (InvalidOperationException ex) when (ex.InnerException != null) //Only catch here if ex.InnerException is not null, otherwise throw { //Actually using System.Diagnostics.Debug.WriteLine - Brought in with 'using static' WriteLine(ex.Message); await LogRectangleMessagesAsync(ex.Message); } finally { WriteLine("Finished processing within the ExceptionMethod"); //Await can be used here also!!! } } /// <summary> /// Logs a message to the console after a short delay. /// </summary> /// <param name="logMessage">The message to log.</param> /// <returns>No result.</returns> private async Task LogRectangleMessagesAsync(string logMessage) { await Task.Delay(3000); Console.WriteLine(string.Concat("Logged: ", logMessage)); } /// <summary> /// Method that simply throws exceptions. /// </summary> private void CallExceptionThrowingMethod() { //Change the exception thrown here to see exception filtering in action within the ExceptionMethod method //throw new InvalidOperationException("A configuration exception occurred!"); throw new InvalidOperationException("An invalid operation was performed!", new InvalidCastException("An invalid cast was performed!")); } #endregion Exception Handling Changes Example } }
using System; using CSharpSixTest.Shapes; //Bring the static members of the Rectangle and Console types directly into scope (meaning they don't need to be fully qualified) using static CSharpSixTest.Shapes.Rectangle; using static System.Console; using Newtonsoft.Json.Linq; namespace CSharpSixTest { /// <summary> /// Main Program Class. /// </summary> class Program { /// <summary> /// Main method for this test drive application. /// </summary> /// <param name="args">Optional input arguments</param> static void Main(string[] args) { //Create a new random Rectangle calling the static Rectangle class GetRandomRectangle method (notice that the type does not to be included here) Rectangle newRectangle = GetRandomRectangle(); //Use static members of System.Console without needed to fully qualify them (with type) WriteLine(newRectangle.Height); WriteLine(newRectangle.Width); WriteLine(newRectangle); WriteLine(newRectangle.Perimeter); //Rectangle to Json call JObject rectangleJson = newRectangle.ToJson(); //Rectangle event hookup (to anonymous method) newRectangle.RectangleFlipped += (sender, e) => { if (sender is Rectangle) { WriteLine($"Flipped Rectangle: { sender as Rectangle }."); } }; //Trigger the event newRectangle.FlipRectangle(); //Pass null/incorrect formed JObjects before passing a valid JObject to convert back to a rectangle Rectangle rect = FromJson(null); Rectangle rect2 = FromJson(new JObject()); Rectangle rect3 = FromJson(rectangleJson); WriteLine(rect3); //Trigger an exception (change Rectangle class to catch exception here) try { newRectangle.ExceptionMethod(); } catch (InvalidOperationException ex) { WriteLine(ex.Message); } //Complete! Invalid Operation Exception will show after this in the current configuration WriteLine("Test Run Complete"); //Await user response ReadKey(); } } }
Have fun exploring the new features and bye for now.