Deconstruction in C# 7

Deconstruction feature can be used to consume tuples. Also, deconstruction feature can be used for user-defined types in .NET but for that you need to provide Deconstruct method.

public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

You can provide multiple overloads of Deconstruct method.

Deconstruction for Tuples

Note: HasValue function used in the below examples returns (bool isValid, int errorCode, string errorMessage) Tuple type.

private static (bool isValid, int errorCode, string errorMessage) HasValue(string inputString) // tuple return type { //Some code }

A. deconstructing declaration

By deconstructing a tuple, you can assign its elements individually to fresh variables as shown below:

(bool isValid, int errorCode, string errorMessage) = HasValue(inputString);

B. deconstructing assignment

By deconstructing a tuple, you can also assign its elements individually to existing variables as shown below:

public static bool IsValidInputStringWithLogging(string inputString, out int errorCode, out string errorMessage) { bool isValid; (isValid, errorCode, errorMessage) = HasValue(inputString); // deconstructing assignment // Some code }

C. discards in deconstruction

C# 7 allows discards in deconstruction, you can ignore the elements of tuple that you don’t require.

Here, we don’t require “errorCode” and “errorMessage”, so we can discard them as shown below:

(bool isValid, _, _) = CSharp7Sample.HasValue(inputString); // deconstructing declaration with discard. Here, we don't require "errorCode" and "errorMessage"

Deconstruction for User-Defined Types

Note: HasValue function used in the below examples returns OperationResult .

private static OperationResult HasValue(string inputString) { //Some code }

A. deconstructor method

For User-Defined Types, you need to provide Deconstruct method to use deconstruction feature.

public class OperationResult { public bool Status { get; set; } public int ErrorCode { get; set; } public string ErrorMessage { get; set; } public void Deconstruct(out bool status, out int errorCode, out string errorMessage) { status = this.Status; errorCode = this.ErrorCode; errorMessage = this.ErrorMessage; } }

B. deconstructor method overload

You can also provide multiple overloaded Deconstruct methods.

public class OperationResult { public bool Status { get; set; } public int ErrorCode { get; set; } public string ErrorMessage { get; set; } public void Deconstruct(out bool status, out string logErrorMessage) { status = this.Status; logErrorMessage = $"{nameof(this.ErrorCode)}: {this.ErrorCode}, {nameof(this.ErrorMessage)}: {this.ErrorMessage}"; } public void Deconstruct(out bool status, out int errorCode, out string errorMessage) { status = this.Status; errorCode = this.ErrorCode; errorMessage = this.ErrorMessage; } }

C. deconstructing declaration

The below sample calls Deconstruct(out isValid, out errorCode, out errorMessage);

(bool isValid, int errorCode, string errorMessage) = HasValue(inputString); // deconstructing declaration

The below sample calls Deconstruct(out isValid, out logErrorMessage);

(bool isValid, string logErrorMessage) = CSharp7Sample.HasValue(inputString); // deconstructing declaration

D. deconstructing assignment

public static bool IsValidInputStringWithLogging(string inputString, out int errorCode, out string errorMessage) { bool isValid; (isValid, errorCode, errorMessage) = HasValue(inputString); // deconstructing assignment // Some code }

E. discards in deconstruction

C# 7 allows discards in deconstruction, you can ignore items returned by a Deconstruct method that you don’t require.

(bool isValid, _, _) = HasValue(inputString); // deconstructing declaration with discard. Here, we don't require "errorCode" and "errorMessage"

Full working sample code to demonstrate the C# 7’s Deconstruction feature-deconstructing declaration, deconstructing assignment, deconstructor method and discards in deconstruction.

Full working sample code to explain the use of Deconstruction for Tuples

CSharp7Sample class contains example to explain: deconstructing declaration, deconstructing assignment and discards in deconstruction.

/// <summary> /// CSharp7Sample. /// </summary> public partial class CSharp7Sample { /// <summary> /// Checks Input String is valid (with logging). /// </summary> /// <param name="inputString">Input string.</param> /// <returns>true or false.</returns> public static bool IsValidInputStringWithLogging(string inputString) { (bool isValid, int errorCode, string errorMessage) = CSharp7Sample.HasValue(inputString); // deconstructing declaration if (isValid == false) { string logMessage = $"{nameof(inputString)}: {inputString}, {nameof(errorCode)}: {errorCode}, {nameof(errorMessage)}: {errorMessage}"; Console.Write(logMessage); } return isValid; } /// <summary> /// Checks Input String is valid (with logging). /// </summary> /// <param name="inputString">Input string.</param> /// <returns>true or false.</returns> public static bool IsValidInputStringWithLogging(string inputString, out int errorCode, out string errorMessage) { bool isValid; (isValid, errorCode, errorMessage) = CSharp7Sample.HasValue(inputString); // deconstructing assignment if (isValid == false) { string logMessage = $"{nameof(inputString)}: {inputString}, {nameof(errorCode)}: {errorCode}, {nameof(errorMessage)}: {errorMessage}"; Console.Write(logMessage); } return isValid; } /// <summary> /// Checks Input String is valid (without logging). /// </summary> /// <param name="inputString">Input string.</param> /// <returns>true or false.</returns> public static bool IsValidInputStringWithoutLogging(string inputString) { (bool isValid, _, _) = CSharp7Sample.HasValue(inputString); // deconstructing declaration with discard. Here, we don't require "errorCode" and "errorMessage" return isValid; } /// <summary> /// Input string has value or not. /// </summary> /// <param name="inputString">Input string.</param> /// <returns>Tuple.</returns> private static (bool isValid, int errorCode, string errorMessage) HasValue(string inputString) // tuple return type - (bool isValid, int errorCode, string errorMessage) { int code = 0; string message = string.Empty; if (inputString == null) { code = 1; message = "Input string is null"; } else if (inputString.Equals(string.Empty)) { code = 2; message = "Input string is empty"; } else if (inputString.Trim().Equals(string.Empty)) { code = 3; message = "Input string only whitespaces"; } bool success = string.IsNullOrEmpty(message); return (isValid: success, errorCode: code, errorMessage: message); // tuple literal - (isValid: success, errorCode: code, errorMessage: message) } }

Full working sample code to explain the use of Deconstruction for User-Defined Types

OperationResult class with overloaded Deconstruct methods.

/// <summary> /// OperationResult. /// </summary> public class OperationResult { /// <summary> /// Gets or sets a value indicating whether status is true. /// </summary> public bool Status { get; set; } /// <summary> /// Gets or sets the error code. /// </summary> public int ErrorCode { get; set; } /// <summary> /// Gets or sets the error message. /// </summary> public string ErrorMessage { get; set; } /// <summary> /// Gets or sets the Error Message for logging. /// </summary> public string LogErrorMessage { get; set; } /// <summary> /// Deconstruct. /// </summary> /// <param name="status">Status.</param> /// <param name="logErrorMessage">Error Message for logging.</param> public void Deconstruct(out bool status, out string logErrorMessage) { status = this.Status; logErrorMessage = $"{nameof(this.ErrorCode)}: {this.ErrorCode}, {nameof(this.ErrorMessage)}: {this.ErrorMessage}"; } /// <summary> /// Deconstruct. /// </summary> /// <param name="status">Status.</param> /// <param name="errorCode">Error Code.</param> /// <param name="errorMessage">Error Message.</param> public void Deconstruct(out bool status, out int errorCode, out string errorMessage) { status = this.Status; errorCode = this.ErrorCode; errorMessage = this.ErrorMessage; } }

CSharp7Sample class contains example to explain: deconstructing declaration, deconstructing assignment and discards in deconstruction.

/// <summary> /// CSharp7Sample. /// </summary> public partial class CSharp7Sample { /// <summary> /// Checks Input String is valid (with logging). /// </summary> /// <param name="inputString">Input string.</param> /// <returns>true or false.</returns> public static bool IsValidInputStringWithLogging(string inputString) { (bool isValid, int errorCode, string errorMessage) = CSharp7Sample.HasValue(inputString); // deconstructing declaration - calls Deconstruct(out isValid, out errorCode, out errorMessage); if (isValid == false) { string logMessage = $"{nameof(inputString)}: {inputString}, {nameof(errorCode)}: {errorCode}, {nameof(errorMessage)}: {errorMessage}"; Console.Write(logMessage); } return isValid; } /// <summary> /// Checks Input String is valid (with logging). /// </summary> /// <param name="inputString">Input string.</param> /// <param name="errorCode">Error Code.</param> /// <param name="errorMessage">Error Message.</param> /// <returns>true or false.</returns> public static bool IsValidInputStringWithLogging(string inputString, out int errorCode, out string errorMessage) { bool isValid; (isValid, errorCode, errorMessage) = CSharp7Sample.HasValue(inputString); // deconstructing assignment - calls Deconstruct(out isValid, out errorCode, out errorMessage); if (isValid == false) { string logMessage = $"{nameof(inputString)}: {inputString}, {nameof(errorCode)}: {errorCode}, {nameof(errorMessage)}: {errorMessage}"; Console.Write(logMessage); } return isValid; } /// <summary> /// Checks Input String is valid (without logging) with discards. /// </summary> /// <param name="inputString">Input string.</param> /// <returns>true or false.</returns> public static bool IsValidInputStringWithoutLogging(string inputString) { (bool isValid, _, _) = CSharp7Sample.HasValue(inputString); // deconstructing declaration with discard. Here, we don't require "errorCode" and "errorMessage" return isValid; } /// <summary> /// Checks Input String is valid (with logging) with Deconstructor Overload. /// </summary> /// <param name="inputString">Input string.</param> /// <returns>true or false.</returns> public static bool IsValidInputStringWithLoggingWithDeconstructorOverload(string inputString) { (bool isValid, string logErrorMessage) = CSharp7Sample.HasValue(inputString); // deconstructing declaration - calls Deconstruct(out isValid, out logErrorMessage); if (isValid == false) { string logMessage = $"{nameof(inputString)}: {inputString}, {logErrorMessage}"; Console.Write(logMessage); } return isValid; } /// <summary> /// Input string has value or not. /// </summary> /// <param name="inputString">Input string.</param> /// <returns>OperationResult.</returns> private static OperationResult HasValue(string inputString) { int code = 0; string message = string.Empty; if (inputString == null) { code = 1; message = "Input string is null"; } else if (inputString.Equals(string.Empty)) { code = 2; message = "Input string is empty"; } else if (inputString.Trim().Equals(string.Empty)) { code = 3; message = "Input string only whitespaces"; } bool success = string.IsNullOrEmpty(message); var result = new OperationResult { ErrorCode = code, ErrorMessage = message, Status = success }; return result; } }

Note: Tuples enhancements needs the ValueTuple types. Add “System.ValueTuple” NuGet package on frameworks that do not include these types.

Happy Coding !!!