DEV Community

Cover image for C# features from 5.0, 6.0 and 7.0
Jai
Jai

Posted on

C# features from 5.0, 6.0 and 7.0

As we know recently c# version 8.0 was launched and it is still in preview as I write this article. Its final version will be released together with .NET Core 3.0. Unlike all the versions of the language so far, not all features of C# 8.0 will be available in the .NET framework. I'll update this blog once version 8.0 is available.

I thought of listing some key features of c# from 5.0, 6.0 and 7.0. I have added Github link also for reference with all the features implementation.

https://github.com/jai00271/CSharpFeatures

Before starting I sincerely want to thank Microsoft and various websites/bloggers.

Fyi I am using asp .Net core 2.2 for all projects.

Let's see how C# has evolved over the years.

alt text

Credit: Image Source

C# 5.0 features

  • Async and await

Async

The keyword is useful for an asynchronous function. If the user specifies the async keyword before the function user needs to call the function asynchronously.

Await

The await keyword is useful when the user calls the function asynchronously.

private static async void Feature1_AsyncAwait()
{
    await DBProcess();
}

private static Task DBProcess()
{
    return Task.Run(() =>
                    {
                        System.Threading.Thread.Sleep(7000);
                    });
}
Enter fullscreen mode Exit fullscreen mode
  • Caller Information

You can fetch caller info using Caller attribute. It is useful for debugging, tracing, etc purpose.

Three different attribute types are used in the caller information.

  1. CallerFilePath: It is used to set information about the caller source code file
  2. CallerMemberName: It is used to set the information about the caller member name
  3. CallerLineNumber: It is used to set the information about the line number of the caller
public static void Feature2_CallerInformation([CallerMemberName] string name = null, [CallerLineNumber] int line = -1, [CallerFilePath] string path = null)
{
    Console.WriteLine("Caller Name: {0}", name);
    Console.WriteLine("Caller FilePath: {0}", path);
    Console.WriteLine("Caller LineNumber: {0}", line);
}

Output:
-------------------------------------------------------------------
Caller Name: Main
Caller FilePath: C:\Users\User_Name\CSharp5\Program.cs
Caller LineNumber: 13
Enter fullscreen mode Exit fullscreen mode

C# 6.0 features

  • Using Static

Now to use any static method we don't have to use class name. We can declare namespace with static keyword for that class.

using static System.Console;

public static void Feature1_UsingStatic()
{
    //Check namespace declaration: using static System.Console;
    WriteLine("We are using Console.WriteLine() to print this line!");
}
Enter fullscreen mode Exit fullscreen mode
  • Auto Property Initializer

Instead of creating properties with default get; and set; we can organize them better by assigning default values. We can also make a property readonly by removing set; while property declaration.

public class Employee
{
    public Employee()
    {
        Salary = 111;
    }
    public string Name { get; set; } = "Jai";
    public int Age { get; set; } = 30;
    public int Salary { get; } = 222;
}
Enter fullscreen mode Exit fullscreen mode

In the above example Salary is readonly and it can't be modified except Employee constructor.

public static void Feature2_AutoPropertyInitializer()
{
    Employee employee = new Employee();
    WriteLine(employee.Name);
    WriteLine(employee.Age);
    WriteLine(employee.Salary);
}
Enter fullscreen mode Exit fullscreen mode

Now in case if you try to set values for the salary you will get below error:

Error

  • Dictionary Initializer

When using dictionaries, sometimes you want to initialize them with values just as you can do with arrays and lists using "=". We can now do it using something called dictionary initializers which work very similar to array initializers.

public static void Feature3_DictionaryInitializer()
{
    Dictionary<int, string> dictionary = new Dictionary<int, string>()
    {
        [1] = "Amar",
        [2] = "Akbar",
        [3] = "Anthony"
    };
    dictionary[4] = "Siddhu";
    foreach (var item in dictionary)
    {
        WriteLine("Dictionary key is: {0} and value is: {1}", item.Key, item.Value);
    }
}
Output:
-------------------------------------------------------------------
Dictionary key is: 1 and value is: Amar
Dictionary key is: 2 and value is: Akbar
Dictionary key is: 3 and value is: Anthony
Dictionary key is: 4 and value is: Siddhu
Enter fullscreen mode Exit fullscreen mode
  • nameOf Expression

This feature is very useful for developers. If you notice above, in all WriteLine methods we have been mentioning Id in string and trying to print the values. Why not use the property name itself and make our life easy.

public static void Feature4_NameOfExpression()
{
    Employee employee = new Employee();
    WriteLine("{0} : {1}", nameof(employee.Name), employee.Name);
    WriteLine("{0} : {1}", nameof(employee.Age), employee.Age);
    WriteLine("{0} : {1}", nameof(employee.Salary), employee.Salary);
}
Output:
-------------------------------------------------------------------
Name : Jai
Age : 30
Salary : 111
Enter fullscreen mode Exit fullscreen mode
  • Exception filters

Exception filters allow us to specify a condition within a catch block so if the condition will return true then the catch block is executed else it won't.

public static void Feature5_ExceptionFilters()
{
    int denominator = 0;

    try
    {
        WriteLine(3 / denominator);
    }
    catch (DivideByZeroException exception) when (denominator == 2)
    {
        WriteLine("If catch executed");
    }
    catch (Exception exception)
    {
        WriteLine(exception.Message);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Await In Catch And Finally Block

Now you can have async calls from catch and finally block. This is very useful as sometime we may want to perform some operation like writing logs, caching, DB calls without blocking the execution.

public static async void Feature6_AwaitInCatchAndFinallyBlock()
{
    int denominator = 0;

    try
    {
        WriteLine(3 / denominator);
    }
    catch (DivideByZeroException exception)
    {
        await FromCatch();
    }
    finally
    {
        await FromFinally();
    }
}

private static async Task FromCatch()
{
    WriteLine("Inside Catch Async call");
}

private static async Task FromFinally()
{
    WriteLine("Inside Finally Async call");
}
Output:
-------------------------------------------------------------------
Inside Catch Async call
Inside Finally Async call
Enter fullscreen mode Exit fullscreen mode
  • Null Conditional Operator

Instead of checking null condition in the traditional way, we can make use of in-line null-conditional operator. This operator helps in removing lot of null's and if conditions.

public class Customer
{
    public int ItemNo { get; set; } = 123;
    public string Name { get; set; } = "Jai";
    public Order Order { get; set; } = new Order();
}
public class Order
{
    public int OrderId { get; set; } = 321;
    public string ProductName { get; set; } = "TShirt";
    public string Description { get; set; } = null;
}
public static void Feature7_NullConditionalOperator()
{
    Customer customer = new Customer();
    WriteLine(customer?.Name);
    WriteLine(customer?.Order?.ProductName);
    WriteLine(customer?.Order?.Description ?? "No description provided");
}
Output:
-------------------------------------------------------------------
Jai
TShirt
No description provided
Enter fullscreen mode Exit fullscreen mode
  • Expression–Bodied Methods

Incase your method just contains one line, you can use expression body method using lambda expression.

public static void Feature8_ExpressionBodiedMethods()
{
    WriteLine(PrintMessage());
}
private static string PrintMessage() => "Have a great day!";
Enter fullscreen mode Exit fullscreen mode
  • Format Strings Using Interpolation

Now you don't need to format string using string.Format(). Trust me this feature has saved lot of my time and I don't have to worry about maintaining format while writing logs statement in code.

public static void Feature9_FormatStringsUsingInterpolation()
{
    Employee employee = new Employee();
    WriteLine($"Name is: {employee.Name}, age is: {employee.Age}");
}
Enter fullscreen mode Exit fullscreen mode

C# 7.0 features

    • out variables

    You can declare out values inline as arguments to the method where they're used.

    alt text

    ​ Credit: Image Source

    public static void Feature1_OutVariable()
    {
        string s = "28-May-2019";
        if (DateTime.TryParse(s, out DateTime date))
        {
            WriteLine(date);
        }
        WriteLine(date);
    }
    
    • Local Functions

    Now we can have local functions instead of multiple separate private function.

    public static void Feature2_LocalFunctions()
            {
                void PrintHi()
                {
                    WriteLine("Hi");
                }
                void PrintHello()
                {
                    WriteLine("Hello");
                }
                PrintHi();
                PrintHello();
            }
    
    • Tuples (with types and literals)

    Return multiple values from a method is now a common practice, we generally use custom datatype, out parameters, Dynamic return type or a tuple object but here C# 7.0 brings tuple types and tuple literals for you it just return multiple values/ multiple type inform of tuple object.

    public static void Feature3_Tuples()
    {
        (string, string, string) PrintDetails()
        {
            //read EmpInfo from database or any other source and just return them
            string name = "Jai";
            string address = "India";
            string moto = "learning";
            return (name, address, moto); // tuple literal
        }
    
        var empInfo = PrintDetails();
        WriteLine($"employee info is {empInfo.Item1} {empInfo.Item2} {empInfo.Item3}");
    }
    

    Tuples are very useful thing where you can replace hash table or dictionary easily, even you can return multiple values for a single key, Additionally you can use it instead of List where you store multiple values at single position.

    .NET also has a Tuple type (See here) but it is a reference type and that leads to performance issue, but C# 7.0 bring a Tuple with value type which is faster in performance and a mutable type.

    • Pattern Matching

    Is pattern matching is a new feature which can used for checking conditions. Pattern matching supports a lot of patterns like Type Pattern, Constant Pattern, Var Pattern, Recursive Pattern, Property Pattern & Property Sub-pattern, Switch Statement, Match Expression, Case expression, Throw expression, De structuring assignment, Testing Nullable, Arithmetic simplification, Tuple decomposition, Complex Pattern, Wildcard Pattern etc.

    public static void Feature4_PatternMatching_If()
    {
        int denominator = 0;
    
        if (denominator is 0)
        {
            WriteLine("Inside If");
        }
    }
    

    Switch pattern helps a lot as it uses any datatype for matching additionally 'case' clauses also can have its pattern so it bit flexible implementation. In below sample switch case checks pattern and call 'Multiply' method

    public class Employee
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public string Gender { get; set; }
        }
        public class Department : Employee
        {
            public string DeptName { get; set; }
            public int Year { get; set; }
        }
    
        public class Performance : Employee
        {
            public string Comment { get; set; }
            public string Bonus { get; set; }
        }
        public static void Feature4_PatternMatching_Switch()
            {
                Employee p = new Employee();
                Department d = new Department()
                {
                    DeptName = "IT",
                    Year = 2006
                };
                Performance a = new Performance()
                {
                    Name = "Jai",
                    Age = 30,
                    Gender = "Male",
                    Bonus = "Sometimes",
                    Comment = "generic Comment"
                };
    
                SwitchCase(a);
                SwitchCase(d);
                SwitchCase(p = null);
    
                void SwitchCase(dynamic instance)
                {
                    switch (instance)
                    {
                        case Employee performer when (performer.Age == 30):
                            WriteLine($"The performer {performer.Name}");
                            break;
                        default:
                            WriteLine("Not found");
                            break;
                        case null:
                            WriteLine("Argument Null Exception");
                            //throw new ArgumentNullException(nameof(a));
                            break;
                    }
                }            
            }
    Output:
    -------------------------------------------------------------------
    The performer Jai
    Not found
    Argument Null Exception
    
    • Ref returns and locals

    Earlier we only had option to use ‘ref’ keyword while passing a parameter to a method. Now with C# 7.0, 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 static void Feature5_RefLocalsAndReturns()
            {
                int[] arr = { 2, 4, 6, 8, 9 };
                ref int oddNum = ref GetFirstOddNumber(arr);
    
                WriteLine($"Odd Number: {oddNum}");
    
                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");
                }
            }
    
    • Throw Expressions

    You can throw exceptions in code constructs that previously weren't allowed because throw was a statement. No need of try catch, Hurray!

    public static void Feature6_ThrowExpressions()
            {
                var denominator = 0;
                WriteLine(Divide(denominator));
    
                double Divide(int denom)
                {
                    var numerator = 3;
                    return denom != 0 ? numerator / denom : throw new DivideByZeroException();
                }
            }
    
    • Generalized async return types

    Methods declared with the async modifier can return other types in addition to Task and Task<T>.

    Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task is a reference type, so using it means allocating an object. In cases where a method declared with the asyncmodifier returns a cached result, or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. It can become costly if those allocations occur in tight loops.

    The new language feature means that async method return types aren't limited to Task, Task<T>, and void. The returned type must still satisfy the async pattern, meaning a GetAwaiter method must be accessible. As one concrete example, the ValueTask type has been added to the .NET framework/ Core to make use of this new language feature:

    public static void Feature7_GeneralizedAsyncReturnTypes()
            {
                NoReturn();
                WriteLine("Returned value is: " + WithReturn().Result);
    
                async ValueTask NoReturn()
                {
                    await Task.Delay(1);
                }
    
                async ValueTask<int> WithReturn()
                {
                    await Task.Delay(1);
                    return 1;
                }
            }
    

    There is a major difference between Task and ValueTask. The Task is a reference type and requires heap allocation. The ValueTask is a value type and is returned by value – meaning, no heap allocation. It’s recommended to use ValueTask when there is a high probability that a method won’t have to wait for async operations. For example, if a method returns cached or predefined results. This can significantly reduce the number of allocations and result in big performance improvement.

    • Expression-Bodied Members

    C# 6 introduced expression-bodied members for member functions, and read-only properties. C# 7.0 expands the allowed members that can be implemented as expressions. In C# 7.0, you can implement constructors, finalizers, and get and set accessors on properties and indexers.

    // Expression-bodied constructor
    public ExpressionMembersExample(string label) => this.Label = label;
    
    // Expression-bodied finalizer
    ~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
    
    private string label;
    
    // Expression-bodied get / set accessors.
    public string Label
    {
        get => label;
        set => this.label = value ?? "Default label";
    }
    

    Thanks for reading.

Top comments (0)