Deconstructors
Usually Constructor would do is, it will create a new object of given type with given parameters. So what the Deconstructor would do is, it will deconstruct the object back into it’s original parts. To be specific,
we have the control of specifying how would you like the object to be deconstructed.
The method must be named
Deconstruct and have a return type of
void.
The parameters to be assigned all must be out parameters, and because they are out parameters with a return type of void, C# allows the Deconstruct method to be overloaded just based on these parameters.
public class Employee
{
public string FirstName { get; }
public string LastName { get; }
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = LastName;
}
}
We'll see how we can add a
Deconstructor to Employee.
public class Employee
{
public string FirstName { get; }
public string LastName { get; }
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = LastName;
}
public void
Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
}
We'll see how to call the Deconstruct.
Employee employee = new Employee("AAA", "BBB");
1. // Example 1: Deconstructing declaration and assignment.
(string firstName , string lastName) = employee;
2. // Example 2: Deconstructing assignment.
string firstName , lastName = null;
(firstName , lastName) = employee;
3. // Example 3: Deconstructing declaration and assignment with var.
var (firstName, lastName) = employee;
The following example shows a Employeeclass with a deconstructor that returns the FirstName and LastName properties:
public class Employee
{
public string FirstName { get; }
public string LastName { get; }
public int Age { get; set; }
public string Email { get; set; }
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = LastName;
}
public void
Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
}
var emp= new Employee
{
FirstName = "FN",
LastName = "LN",
Email = "test@example.com",
Age = 99
};
(var firstName, var lastName) = user;
We are creating a Employee object, and then deconstructing it into the firstName and lastName variables, which are declared as part of the deconstruction.
Here we can notice C# is allowing
simultaneous assignment to multiple variables of different values. This is not the same as the null assigning declaration in which all variables are initialized to the same value (null):
string firstName , lastName = null;
The C# 7.0
tuple-like syntax requires that
at least two variables appear within the parentheses. For example,
(FileInfo path) = pathInfo; is not allowed even if a deconstructor exists for:
public void Deconstruct(out FileInfo file)
With the new tuple-like syntax,
each variable is assigned a different value corresponding not to its name, but to the order in which it appears in the declaration and the deconstruct statement.
In other words,
we can’t use the C# 7.0 deconstructor syntax for Deconstruct methods with only one out parameter.
Deconstruct() has
the ability to split a tuple’s values up and put them into variables. In the above expamples, we can see how you take a tuple with a string and int and deconstruct them into variables.
Deconstruct also does not need to be directly attached to the class. C# allows the method to be implemented as an
extension method as well.
public class
AbstractClassA
{
public string UserID{ get; }
public string IPAddress{ get; }
}
static class
AbstractExtensions
{
public static void Deconstruct
(this AbstractClassA obj, out string userID, out string ipAddress)
{
userID= obj.UserID
ipAddress= obj.IPAddress;
}
}
class Request : AbstractClassA
{
public FirstAndLastNameNoDeconstructor(string userID, string ipAddress)
{
UserID = userID
IPAddress = ipAddress;
}
}
Request obj = new Request ("A001", "127.0.0.1")
var(userID, ipAddress) = obj;
Pattern Matching with the Switch Statement
C# 7.0 allows user to
use pattern in IS statement and with SWITCH statement, so we can match pattern with any datatype, patterns can be constant patterns, Type patterns, Var patterns. following sample snippet will clear your concepts, let's start with IS pattern
public void PrintStars(object o)
{
if (o is null) return; // constant pattern "null"
if (!(o is int i)) return; // type pattern "int i"
OR
if (o is int i || (o is string s && int.TryParse(s, out i))
WriteLine(new string('*', i));
}
Switch statements with patterns
1. Patterns can be used in case clauses
2. Case clauses can have additional conditions on them
switch(shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("<unknown shape>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
Local Functions
Local methods and functions is already there in current version of C# (Yes, we can achieve them using Deleage, Func and Action types), but still there are some limitations to local method, we can not have following features in it
1. Generic
2. out parameters
3. Ref
4. params
Example:
bool IsPalindrome(string text)
{
if (string.IsNullOrWhiteSpace(text)) return false;
bool LocalIsPalindrome(string target)
{
target = target.Trim(); // Start by removing any surrounding whitespace.
if (target.Length <= 1) return true;
else
{
return char.ToLower(target[0]) ==
char.ToLower(target[target.Length - 1]) &&
LocalIsPalindrome(
target.Substring(1, target.Length - 2));
}
}
return LocalIsPalindrome(text);
}
Return by Reference
Since C# 1.0 it has been possible to pass arguments into a function by reference (ref). The result is that any change to the parameter itself will get passed back to the caller.
In C# 7, we can also use ‘ref’ for returning a variable from a method i.e. a method can return variable with reference. We can also store a local variable with reference.
public ref int GetFirstOddNumber(int[] numbers) {
for (int i = 0; i < numbers.Length; i++) {
if (numbers[i] % 2 == 1) {
return ref numbers[i];
}
}
throw new Exception("odd number not found");
}
int[] x = { 2, 4, 62, 54, 33, 55, 66, 71, 92 };
ref int oddNum = ref GetFirstOddNumber(x);
Console.WriteLine($"\t\t\t\t\t{oddNum}");
oddNum = 35;
for(int i=0; i < x.length; i++)
{
Console.WriteLine($"{x[i]}\t");
}
Th output:
33
2 4 62 54 35 55 66 71 92
The GetFirstOddNumber(), retruns 33 value and it's holding by oddNum and after we have value, we modified the value and it is reflecting to the main collection.
When
declaring a reference local variable, initialization is required. This involves assigning it a ref return from a function or a reference to a variable:
ref string text; // Error
We can’t declare a by reference type for an auto-implemented property:
class Thing { ref string Text { get;set; } /* Error */ }
Properties that return a reference are allowed:
class Thing { string _Text = "Inigo Montoya";
ref string Text { get { return ref _Text; } } }
A reference local variable can’t be initialized with a value (such as null or a constant). It must be assigned from a by reference returning member or a local variable/field:
ref int number = null; ref int number = 42; // ERROR
Out Variables
If you had to pass a variable as out parameter, then the variable must had been declared before passing it to the method. But in C# 7, you can declare variable directly when you are passing it inside a method.
Throw Expressions
In C# 7, we can throw an exception directly through expression. Thus, an exception can be thrown from an expression.
static void Main(string[] args)
{
var a = Divide(10,0);
}
public static double Divide(int x, int y)
{
return y!=0? x %y : throw new DivideByZeroException();
}
Record Type
C# support record type, which is nothing but a container of a properties and variables, most of the time classes are full with properties and variables, we need lot of code to just declare them but with the help of Record Type you can reduce your effort, see below snippet
class studentInfo
{
string _strFName;
string _strMName;
string _strLName;
studentInfo(string strFN, string strMN, string strLN){
this._strFName = strFN;
this._strMName = strMN;
this._strLName = strLN;
}
public string StudentFName {get{ return this._strFName;}}
public string StudentMName {get{ return this._strMName;}}
public string StudentLName {get{ return this._strLName;}}
}
In above code we have a class with property, constructor and variable, so access and declare variable i need to write more code.
To avoid it i can use Record Type in C#, see below snippet
class studentInfo(string StudentFName, string StudentMName, string StudentLName);
That's it and we have Done !
Non-'NULL' able reference type
Null reference is really a headache for all programmers, it is a million dollar exception. If you don't check them you got runtime exception or if you check them for each object then your code goes long and long, To deal with this problem C# 7.0 come with non-nullable reference types
**I think syntax for it yet not fixed still they have release following syntax
'?' is for nullable value-type and '!' is for non-nullable reference type
Hide Copy Code
int objNullVal; //non-nullable value type
int? objNotNullVal; //nullable value type
string
! objNotNullRef; //non-nullable reference type
string objNullRef; //nullable reference type
Now look at the following complier effect after we run this snippet
Hide Copy Code
MyClass objNullRef; // Nullable reference type
MyClass! objNotNullRef; // Non-nullable reference type
objNullRef = null; // this is nullable, so no problem in assigning
objNotNullRef = null; // Error, as objNotNullRef is non-nullable
objNotNullRef = objNullRef; // Error, as nullable object can not be refered
WriteLine(objNotNullRef.ToString()); // Not null so can convert to tostring
WriteLine(objNullRef.ToString()); // could be null
if (objNullRef != null) { WriteLine(objNullRef.ToString); } // No error as we have already checked it
WriteLine(objNullRef!.Length); // No error
More Expression-Bodied Members
C# 6.0 introduced expression-bodied members for functions and properties, enabling a streamlined syntax for implementing trivial methods and properties. In C# 7.0, expression-bodied implementations are added to constructors, accessors (get and set property implementations) and even finalizers (see Figure 11).
Figure 11 Using Expression-Bodied Members in Accessors and Constructors
class TemporaryFile // Full IDisposible implementation
// left off for elucidation.
{
public TemporaryFile(string fileName) =>
File = new FileInfo(fileName);
~TemporaryFile() => Dispose();
Fileinfo _File;
public FileInfo File
{
get => _File;
private set => _File = value;
}
void Dispose() => File?.Delete();
}