New C# 7 features in action: Deconstruction in C# 7 – deconstructing declaration, deconstructing assignment, deconstructor method and discards in deconstruction.

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 !!!

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *