Most of you are already aware that Visual Studio 2017 RC has been released and it comes with a lot of new features. In this article, I am going to discuss only about C# 7 features. The other features will be explained in my upcoming articles.
This is my third article on C# 7. Before this, I have given update on C# 7 about 7-8 months ago, and explained the new things coming in C# 7.
In C# 7, a lot of new features have been introduced, and also enhancement have been done for some features. I will be covering all those things in context of Visual Studio 2017 RC. Given below is the list of features which I am going to explain in this article.
- Local functions or nested functions
- Binary Literal
- Digit Separators
- Pattern matching
- ref returns and ref Locals
- Tuples (Tuples Enhancement)
- Throw Expressions
- Expression Bodied Members (some more expression bodied members in C# 7)
- Expression bodied Methods (C# 6.0)
- Expression bodied Properties (C# 6.0)
- Expression bodied constructor (C# 7.0)
- Expression bodied destructor (C# 7.0)
- Expression bodied getters (C# 7.0)
- Expression bodied setters (C# 7.0)
- Out variables (enhancement for out variables)
- Deconstruction
In the above list, I have mentioned only 10 new features of C# 7 but there are some more features like Generalized async (Generalized async return), Replace (Partial Class Enhancements) Records (or Record Types) Non-Nullable Reference Types, Immutable Types etc.
Some of the features are not yet supported in Visual Studio 2017 RC and are still under development. Even C# 7 has not been developed completely and still it is in preview mode. You can ignore some of the features of C# 7 right now and focus on the features which can be tested in Visual Studio 2017 RC. Below is a pictorial representation of the major features of C# 7 which are supported in Visual Studio 2017 RC.
Some of the features are not yet supported in Visual Studio 2017 RC and are still under development. Even C# 7 has not been developed completely and still it is in preview mode. You can ignore some of the features of C# 7 right now and focus on the features which can be tested in Visual Studio 2017 RC. Below is a pictorial representation of the major features of C# 7 which are supported in Visual Studio 2017 RC.
- Local functions or nested functions
Using nested functions inside a function is called local function. To understand how local function works, just have a look at the below code and see how you are going to write and call Add() and Multiply() methods.
Example 1- using static System.Console;
- namespace UseLocalFunctions {
- class Program {
- static void Main(string[] args) {
- void Add(int x, int y) {
- WriteLine($ "Sum of {x} and {y} is : {x + y}");
- }
- void Multiply(int x, int y) {
- WriteLine($ "Multiply of {x} and {y} is : {x * y}");
- Add(30, 10);
- }
- Add(20, 50);
- Multiply(20, 50);
- }
- }
- }
In the above example, I have used 2 nested functions “Add” & “Multiply” and you can see that those can be called from anywhere inside the parent function even though I have called inline function “Add” inside another inline function Multiply.
Example 2
In the first example, you have seen that nested function is returning void but there are no such restrictions and it can also return any other type. In this example, I am going to create a local function “CalculateMarks” which will calculate total marks and return it as decimal datatype.- public static void PrintStudentMarks(int studentId, params Subject[] subjects) {
- WriteLine($ "Student Id {studentId} Total Marks: {CalculateMarks()}");
- WriteLine($ "Subject wise marks");
- foreach(var subject in subjects) {
- WriteLine($ "Subject Name: {subject.SubjectName} \t Marks: {subject.Marks}");
- }
- decimal CalculateMarks() {
- decimal totalMarks = 0;
- foreach(var subject in subjects) {
- totalMarks += subject.Marks;
- }
- return totalMarks;
- }
- }
- class Program {
- static void Main(string[] args) {
- PrintStudentMarks(101, new Subject {
- SubjectName = "Math", Marks = 96
- }, new Subject {
- SubjectName = "physics", Marks = 88
- }, new Subject {
- SubjectName = "Chem", Marks = 91
- });
- }
- public static void PrintStudentMarks(int studentId, params Subject[] subjects) {
- WriteLine($ "Student Id {studentId} Total Marks: {CalculateMarks()}");
- WriteLine($ "Subject wise marks");
- foreach(var subject in subjects) {
- WriteLine($ "Subject Name: {subject.SubjectName} \t Marks: {subject.Marks}");
- }
- decimal CalculateMarks() {
- decimal totalMarks = 0;
- foreach(var subject in subjects) {
- totalMarks += subject.Marks;
- }
- return totalMarks;
- }
- }
- public class Subject {
- public string SubjectName {
- get;
- set;
- }
- public decimal Marks {
- get;
- set;
- }
- }
- }
Example 3
So far, I have explained the use of local function for printing some values or for returning a calculated variable from local function but apart from that, you can do a lot more. In this example, I am going to explain how we can use local function instead of recursive function.
Calculate factorial with recursive function.- private static long GetFactorial(int number) {
- if (number < 0) throw new ArgumentException("negative number", nameof(number));
- if (number == 0) return 1;
- return number * GetFactorial(number - 1);
- }
- private static long GetFactorialUsingLocal(int number) {
- if (number < 0) throw new ArgumentException("negative number", nameof(number));
- if (number == 0) return 1;
- long result = number;
- while (number > 1) {
- Multiply(number - 1);
- number--;
- }
- void Multiply(int x) => result *= x;
- return result;
- }
Local function does not need to maintain call stack where recursive function maintains call stack.
Image: Screenshot of call stack maintained by recursive function
Image: Screenshot of call stack maintained by local function
So, in the above screenshot, you can see that no extra call stack has been maintained.
I want to mention one more thing for new readers (who are not aware about call stack limitation). If your call stack is too long, your application will fail. Let’s try the same application for calculation of factorial of 9K.
You can see that the recursive function is throwing “System.StackOverflowException” exception and application has crashed.
Now, call local method to calculate the factorial of 9k.
Below is the complete code for the same.- class Program {
- static void Main(string[] args) {
- //BigInteger factorial = GetFactorial(9000);
- BigInteger factorial = GetFactorialUsingLocal(9000);
- }
- private static BigInteger GetFactorialUsingLocal(int number) {
- if (number < 0) throw new ArgumentException("negative number", nameof(number));
- else if (number == 0) return 1;
- BigInteger result = number;
- while (number > 1) {
- Multiply(number - 1);
- number--;
- }
- void Multiply(int x) => result *= x;
- return result;
- }
- private static BigInteger GetFactorial(int number) {
- if (number < 0) throw new ArgumentException("negative number", nameof(number));
- return number == 0 ? 1 : number * GetFactorial(number - 1);
- }
- }
- Binary Literals
As you know that before C# 7, two types of notations were supported in C# - decimal literal and hexadecimal literal.
Decimal Literal
int length = 50;
Hexadecimal Literal
int number = 0X3E8;
or
int number = 0x3E8;
So, you can see that when you are not using any prefix with integer data type, then it is by default denoting a number in decimal system whereas for a hexadecimal number, we need to use prefix “0X” or “0x” (you can use either small ‘x’ or capital ‘X’).
But, when you print it using Console.WriteLine(), then it will always print it in decimal value. Below is an example for the same.- int Int32MaxLengthInDecimal = 2147483647;
- int Int32MaxLengthInHexaDecimal = 0x7FFFFFFF;
- WriteLine($ "Int32MaxLengthInDecimal {Int32MaxLengthInDecimal}");
- WriteLine($ "Int32MaxLengthInHexaDecimal {Int32MaxLengthInHexaDecimal}");
In C# 7, the same way, you can also declare a binary literal. You can use “0B” or “0b” for binary literal. So, in C# 7, you can represent integer in 3 ways - decimal literal, hexadecimal literal, and binary literal.- Binary Literal
- int x = 0B110010;
- or
- int x = 0b110010;
- using static System.Console;
- namespace BinaryLiteral {
- class Program {
- static void Main(string[] args) {
- //Represent 50 in decimal, hexadecimal & binary
- int a = 50; // decimal representation of 50
- int b = 0X32; // hexadecimal representation of 50
- int c = 0B110010; //binary representation of 50
- //Represent 100 in decimal, hexadecimal & binary
- int d = 50 * 2; // decimal represenation of 100
- int e = 0x32 * 2; // hexadecimal represenation of 100
- int f = 0b110010 * 2; //binary represenation of 100
- WriteLine($ "a: {a:0000} b: {b:0000} c: {c:0000}");
- WriteLine($ "d: {d:0000} e: {e:0000} f: {f:0000}");
- }
- }
- }
- Digit Separators
Digit separator is also a new feature in C# 7. We can use one or more than one Underscore( _ ) character for digit separators. Sometimes, it is required when we are going to represent a big number. So, the code snippet would look something like.- using static System.Console;
- namespace DigitSeparators {
- class Program {
- static void Main(string[] args) {#
- region Using Digit Separators
- int binaryData = 0B0010 _0111_0001_0000; // binary value of 10000
- int hexaDecimalData = 0X2B _67; //HexaDecimal Value of 11,111
- int decimalData = 104 _567_789;
- int myCustomData = 1 ___________2__________3___4____5_____6;
- double realdata = 1 _000 .111 _1e1_00;
- WriteLine($ " binaryData :{binaryData} \n hexaDecimalData: {hexaDecimalData} \n decimalData: {decimalData} \n myCustomData: {myCustomData} \n realdata: {realdata}");#
- endregion
- }
- }
- }
- Pattern matching
C# supports the feature of pattern matching. We can do a lot more using this. Below is a simple code snippet.- using static System.Console;
- namespace PatternMatching {
- class Program {
- static void Main(string[] args) {
- //Example 1
- var myData = "Custom Data";
- var myData2 = myData is string ? "String" : "Not a string";
- var myData3 = myData is string a ? a : "Not a String";
- WriteLine(myData2);
- WriteLine(myData3);
- //Example 2
- var x = 10;
- dynamic y = 0b1001;
- var sum = y is int ? $ "{y * x}" : "Invalid data";
- WriteLine(sum);
- }
- }
- }
- Out Variables (enhancement for out variables)
Before C# 7, 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.
In the above screenshot, you can see that for C# 7 I am declaring an out variable inside method and it is being accessed from outside also.- using static System.Console;
- class Program {
- static void Main(string[] args) {
- string s = "26-Nov-2016";
- if (DateTime.TryParse(s, out DateTime date)) {
- WriteLine(date);
- }
- WriteLine(date);
- }
- Tuples
In C# 7, we can use Tuples directly i.e. in more convenient way. Below is a sample code- //returning price & discount
- (int, int) GetPrice(int itemId) {
- var product = (500, 100);
- return product;
- }
Below is a screenshot of error which you will get at the first attempt.
Now, move to its solution.
Solution
Install “System.ValueTuple” from NuGet Package.- Select your project.
- Right click on it.
- Select “Manage NuGet Packages…” from context menu.
- Search for “System.ValueTuple” and install it.
Now, re-build your solution. You will notice that error has been removed and your application is built successfully.
Now, you may be thinking how to utilize or call this tuple method. It’s very simple. Just have a look at the below code snippet.- using static System.Console;
- using static System.Text.Encoding;
- namespace TupleExampleByBNarayan {
- class Program {
- static void Main(string[] args) {
- OutputEncoding = UTF8;
- Program p = new Program();
- var price = p.GetPrice(1);
- WriteLine($ "Price: ₹{price.Item1}/- \nDiscount: ₹{price.Item2}/-");
- }
- //returning price & discount
- (int, int) GetPrice(int itemId) {
- var product = (500, 100);
- return product;
- }
- }
- }
But, if you watch the above code snippet closely, you will notice the naming convention issues. I am accessing price with property “Item1” and discount with name “Item2” but it would be better if I have accessed it using their names directly. We can also do that.
Let’s have a look at the below code snippet.- using static System.Console;
- using static System.Text.Encoding;
- namespace TupleExampleByBNarayan {
- class Program {
- static void Main(string[] args) {
- OutputEncoding = UTF8;
- Program p = new Program();
- var product = p.GetPrice(1);
- WriteLine($ "Price: ₹{product.price}/- \nDiscount: ₹{product.discount}/-");
- }
- //returning price & discount
- (int price, int discount) GetPrice(int itemId) {
- var product = (500, 100);
- return product;
- }
- }
- }
Internally, you can use any variable name for tuple but externally, it will always return with the same name that is written as return type.- (int price, int discount) GetPrice(int itemId) {
- var product = (500, 100);
- return product;
- }
- And(int price, int discount) GetPrice(int itemId) {
- var product = (a: 500, b: 100);
- return product;
- }
- Deconstruction (splitting tuples)
Till now, you have seen that we can access tuples in different ways like accessing with “Item1”, “Item2” or with some meaningful names, like price and discount. Still, we need to access those properties with a collection name like “product.price”. A question must be arising in your mind - can I use the variable name “price” directly without using any other collection name. The answer is obviously YES.
We can say that “deconstruction” is a concept which allows us to access a tuple member directly with its name without using any tuple variable name. Or, you can also say that “deconstruction” is a concept which allows us to split tuple members and we can use it directly with their respective names. - Expression bodied members
Expression bodied member is not a new concept in C#7. It has been introduced with C#6. But in C#7, some more enhancement has been done for expression bodied members. If you are not aware about expression bodied members, then try to understand it from beginning.
Below is a screenshot from my previous article about anonymous methods and lambda expressions
If you want to go in depth, then please go through these 2 articles.
C# 6.0
Expression bodied Methods
Expression bodied Properties
C# 7.0
Expression bodied constructor
Expression bodied destructor
Expression bodied getters
Expression bodied setters
Expression Bodied Methods
In C# 6, Microsoft introduced a new feature Expression-bodied Methods which was very similar to and inspired by anonymous & lambda expression. But there are some differences between these two.
In the case of Expression-bodied methods, it must have a return type, name, and returned expression.
We can use access modifiers (private, public, protected, internal and protected internal) with expression-bodied method. We can declare it as virtual, static, or it can also override its parent class method. It can be asynchronous if it is returning void.
Example 1
Expression bodied method has access modifier as public and return type string.
Code Snippet- class User {
- public int UserId {
- get;
- set;
- }
- public string UserName {
- get;
- set;
- }
- public string EmailId {
- get;
- set;
- }
- public string ContactNumber {
- get;
- set;
- }
- public string GetUserDetails() => $ "{UserId} + {UserName} + {EmailId} + {ContactNumber}";
- }
Expression Bodied Properties
Code Snippet- class User {
- public int UserId {
- get;
- } = 1001;
- public string UserName {
- get;
- } = "BNarayan";
- public string EmailId {
- get;
- } = "bnarayan....@gmail.com";
- public string ContactNumber {
- get;
- } = "99xxxxxxxx";
- public string UserDetails => $ "{UserId} {UserName} {EmailId} {ContactNumber}";
- }
Expression bodied constructor
In C# 7, we can declare a constructor as “expression bodied members”. Below is a code snippet for the same.- class Product {
- public int ProductId {
- get;
- } = 1;
- public string ProductName {
- get;
- }
- public decimal Price => 3000;
- Product() => ProductName = "Microsoft HoloLens";
- }
Code Snippet- class Product {
- public int ProductId {
- get;
- } = 1;
- public string ProductName {
- get;
- }
- public decimal Price => 3000;
- Product() => ProductName = "Microsoft HoloLens";
- ~Product() => WriteLine("Expression bodied destructor");
- }
Code Snippet- class Product {
- Dictionary < int, decimal > productPriceList = new Dictionary < int, decimal > ();
- public int ProductId {
- get;
- set;
- } = 1;
- public string ProductName => "Microsoft HoloLens";
- public decimal Price {
- get => productPriceList[ProductId];
- set => productPriceList[ProductId] = value;
- }
- }
- class Product {
- Dictionary < int, decimal > productPriceList = new Dictionary < int, decimal > ();
- public int _productId;
- public string ProductName => "Microsoft HoloLens";
- public decimal Price {
- get => productPriceList[_productId];
- set => productPriceList[_productId] = value;
- }
- }
- Ref returns and locals
Most of the developers must be aware about C# “ref” keyword and its behaviors. As we know that it returns address of the variable rather than value and it can also be used with reference type where new memory allocation has to be done in the calling method. If anyone is not aware about ‘ref’ keyword or wants to brush basic concept of C#, then please go through the article.
Basic Interview Tips in C#
But till C# 6, we can only use ‘ref’ keyword while passing a parameter for method. 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.
Ref Return
Sample Code- int[] x = {
- 2,
- 4,
- 62,
- 54,
- 33,
- 55,
- 66,
- 71,
- 92
- };
- 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");
- }
- But there are some restrictions and everything cannot be returned as reference like below code will give error: Dictionary < int, decimal > ProductPriceList = new Dictionary < int, decimal > ();
- public ref decimal GetProductPriceReference(int productId) {
- return ref ProductPriceList[productId];
- }
CS8156 An expression cannot be used in this context because it may not be returned by reference
Ref locals
As you have seen, in the previous example I am searching for an odd number inside an integer array and if it is not found throwing exception, the method is not returning it as value but as reference. So, you need to store that value also which has been returned as reference. To store it in a local variable, we can use ‘ref’ keyword with local variables, known as ref locals.
Code snippet- int[] x = { 2, 4, 62, 54, 33, 55, 66, 71, 92 };
- ref int oddNum = ref GetFirstOddNumber(x);
- using static System.Console;
- namespace RefReturnsAndRefLocals {
- class Program {
- public ref int GetFirstOddNumber(int[] numbers) {
- for (int i = 0; i < numbers.Length; i++) {
- if (numbers[i] % 2 == 1) {
- return ref numbers[i]; //returning as reference
- }
- }
- throw new Exception("odd number not found");
- }
- static void Main(string[] args) {
- Program p = new Program();
- int[] x = {
- 2,
- 4,
- 62,
- 54,
- 33,
- 55,
- 66,
- 71,
- 92
- };
- ref int oddNum = ref p.GetFirstOddNumber(x); //storing as reference
- WriteLine($ "\t\t\t\t{oddNum}");
- oddNum = 35;
- for (int i = 0; i < x.Length; i++) {
- Write($ "{x[i]}\t");
- }
- ReadKey();
- }
- }
- }
In the above screenshot, you can see that first time the variable “OddNum” is storing the value 33 with its reference inside the array ‘x’.
If you print “oddNum” first time, then it will print 33 but after that, I have re-assigned its value and set “oddNum =35” now iterating the array and printing elements of array and you can see that whatever I have done, modification for “oddNum” from outside is also reflecting inside the array and internal value has been modified from 33 to 35. - Throw Expressions
In C# 7, we can throw an exception directly through expression. Thus, an exception can be thrown from an expression.
Code Snippet- class Program {
- 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();
- }
- } // This is just a sample script. Paste your real code (javascript or HTML) here.
- if ('this_is' == /an_example/) {
- of_beautifier();
- } else {
- var a = b ? (c % d) : e[f];
- }
Apart from the above-mentioned features, there are some more features for C# 7, that have been proposed like generalized async (Generalized async return), Replace (Partial Class Enhancements) Records (or Record Types) Non-Nullable Reference Types, Immutable Types etc.
I will discuss these features later on with future release VS 2017.
No comments:
Post a Comment