Dot Net For All

C# Evolution – What’s new in Each version

Hello friends, In this article I will discuss about the evolution of C# from C# 2.0 to C# 8.0. This article will include a brief summary of all the features which has been added to all the released version of C#. This is the most comprehensive list to get the latest additions to each version of C#.

This article will be of great help to you if you are a C# developer and want to keep yourself updated with C# features. I have included the features in chronological order.

Please note that I have not discussed all the features in details in this post. I have provided relevant links for the features from this blog.

I will regularly update this post.

C# Evolution in chronological Order

What was new in C# 2.0

The biggest addition to C# 2.0 was generics. Generics allowed us to have a template class and we can provide the type parameter to all Generic class.

The other addition was Nullable types. Nullable types allowed to set NULL value for the value types.

Iterators was introduced in C# 2.0. Iterator like foreach helps to traverse the collection. Yield return and yield break helps to maintain the cursor and current state in the collection.

Another addition was anonymous method. These are basically the predecessor of the lambda expressions.

How to create a anonymous method?

    public delegate int MyDelegate(int i);
    public class MyNewClass
    {
        MyDelegate del => delegate(int x) { return x * x; };        
        public void MyMethod()
        {
            del(5);
        }
    }

Above is a small code snippet to create a anonymous method. Alternatively the first line in the above code can be written using lambda function as below

MyDelegate del = (int x) => { return x * x; };        

Partial classes and static classes were also introduced in C# 2.0.

What was new in C# 3.0

Most of the features of C# 3.0 were related to LINQ. Language Integrataed Query or LINQ as it is most commonly known is used to write SQL like queries in C# language. And checked statically for correctness, and query both local collections (such as lists or XML documents) or remote data sources (such as a database)

Introduction of var keyword:

var keyword var keyword, which tells the compiler to implicitly type a local variable.

var x = "hello";<br>
var y = new System.Text.StringBuilder();
var z = (float)Math.PI;<br>
1
2
3

var x = "hello";
var y = new System.Text.StringBuilder();
var z = (float)Math.PI;

Because of this direct equivalence, implicitly typed variables are statically typed. For example, the following generates a compile-time error

var x = 5;
x = "hello";    // Compile-time error; x is of type int
1
2

One of the main use of var keyword is while working with LINQ queries where we don’t know the type.

Object initializers helps us to initialize the properties of the object while constructing the instance. It is valid for anonymous types as well.

Lambda expressions :

You can read more about lambda expression in one of my article here.

Extension methods :

Extend an existing type with new methods (without altering the type’s definition), making static methods feel like instance methods. LINQ’s query operators are implemented as extension methods. More about extension methods in this article.

Query expressions provide a higher-level syntax for writing LINQ queries that can be substantially simpler when working with multiple sequences or range variables.

Expression trees are miniature code DOMs (Document Object Models) that describe lambda expressions assigned to the special type Expression. Expression trees make it possible for LINQ queries to execute remotely (e.g., on a database server) because they can be introspected and translated at runtime (e.g., into a SQL statement).

C# 3.0 also added automatic properties and partial methods.

Automatic properties cut the work in writing properties that simply get/set a private backing field by having the compiler do that work automatically. Partial methods let an auto-generated partial class provide customizable hooks for manual authoring that “melt away” if unused.
What’s New in C# 4.0

What’s New in C# 4.0

The features new to C# 4.0 were:

Dynamic binding defers binding—the process of resolving types and members—from compile time to runtime and is useful in scenarios that would otherwise require complicated reflection code. Dynamic binding is also useful when interoperating with dynamic languages and COM components.

Optional parameters allow functions to specify default parameter values so that callers can omit arguments and named arguments allow a function caller to identify an argument by name rather than position.

Type variance rules were relaxed in C# 4.0 , such that type parameters in generic interfaces and generic delegates can be marked as covariant or contravariant, allowing more natural type conversions.

COM interoperability was enhanced in C# 4.0 in three ways. First, arguments can be passed by reference without the ref keyword (particularly useful in conjunction with optional parameters). Second, assemblies that contain COM interop types can be linked rather than referenced. Linked interop types support type equivalence, avoiding the need for Primary Interop Assemblies and putting an end to versioning and deployment headaches. Third, functions that return COM-Variant types from linked interop types are mapped to dynamic rather than object, eliminating the need for casting.

What’s New in C# 5.0

C# 5.0’s addition was async and await functionality. This feature helps to achieve asynchronous operations with much ease as compared to earlier versions of C#. You can read more about async await in this article.

What’s New in C# 6.0

C# 6.0, which shipped with Visual Studio 2015, features a new-generation compiler, completely written in C#. Known as project “Roslyn,” the new compiler exposes the entire compilation pipeline via libraries, allowing you to perform code analysis on arbitrary source code. The compiler itself is open source, and the source code is available at github.com/dotnet/roslyn.

In addition, C# 6.0 features a number of minor, but significant enhancements, aimed primarily at reducing code clutter.

The null-conditional (“Elvis”) operator avoids having to explicitly check for null before calling a method or accessing a type member. In the following example, result evaluates to null instead of throwing a NullReferenceException:

System.Text.StringBuilder sb = null;
string result = sb?.ToString();      // result is null

Expression-bodied functions allow methods, properties, operators, and indexers that comprise a single expression to be written more tersely, in the style of a lambda expression:

public int TimesTwo (int x) => x * 2;
public string SomeProperty => "Property value";

Property initializers let you assign an initial value to an automatic property:

public DateTime TimeCreated { get; set; } = DateTime.Now;

Initialized properties can also be read-only:

public DateTime TimeCreated { get; } = DateTime.Now;

Read-only properties can also be set in the constructor, making it easier to create immutable (read-only) types. Index initializers allow single-step initialization of any type that exposes an indexer:

var dict = new Dictionary<int,string>()
{
  [3] = "three",
  [10] = "ten"
};

String interpolation offers a brief and easily understandable alternative to string.Format:

string s = $"It is {DateTime.Now.DayOfWeek} today";

Exception filters let you apply a condition to a catch block:

string html;
try
{
  html = new WebClient().DownloadString ("http://asef");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
  ...
}

The using static directive lets you import all the static members of a type, so that you can use those members unqualified:

using static System.Console;
...
WriteLine ("Hello, world");  // WriteLine instead of Console.WriteLine

The nameof operator returns the name of a variable, type, or other symbol as a string. This avoids breaking code when you rename a symbol in Visual Studio:

int capacity = 123;
string x = nameof (capacity);   // x is "capacity"
string y = nameof (Uri.Host);   // y is "Host"

And finally, you’re now allowed to await inside catch and finally blocks

What’s New in C# 7.0

(C# 7.0 ships with Visual Studio 2017.)
Numeric literal improvements

Numeric literals in C# 7 can include underscores to improve readability. These are called digit separators and are ignored by the compiler:

int million = 1_000_000;

Binary literals can be specified with the 0b prefix:

var b = 0b1010_1011_1100_1101_1110_1111;

Out variables and discards

C# 7 makes it easier to call methods that contain out parameters. First, you can now declare out variables on the fly:

bool successful = int.TryParse ("123", out int result);<br>
Console.WriteLine (result);

And when calling a method with multiple out parameters, you can discard ones you’re uninterested in with the underscore character:

SomeBigMethod (out _ , out _, out _ , out int x, out _, out _ , out _);
Console.WriteLine (x);

Patterns

You can also introduce variables on the fly with the is operator. These are called pattern variables

void Foo (object x)
{
  if (x is string s)
    Console.WriteLine (s.Length);
}

The switch statement also supports patterns, so you can switch on type as well as constants . You can specify conditions with a when clause, and also switch on the null value:

switch (x)
{
  case int i:
    Console.WriteLine ("It's an int!");
    break;
  case string s:
    Console.WriteLine (s.Length);   // We can use the s variable
    break;
  case bool b when b == true:        // Matches only when b is true
    Console.WriteLine ("True");
    break;
  case null:
    Console.WriteLine ("Nothing");
    break;
}

Local methods

A local method is a method declared inside another function):

void WriteCubes()
{
  Console.WriteLine (Cube (3));
  Console.WriteLine (Cube (4));
  Console.WriteLine (Cube (5));

  int Cube (int value) => value * value * value;
}

Local methods are visible only to the containing function, and can capture local variables in the same way that lambda expressions do.
More expression-bodied members

C# 6 introduced the expression-bodied “fat-arrow” syntax for methods, read-only properties, operators, and indexers. C# 7 extends this to constructors, read/write properties, and finalizers:

public class Person
{
  string name;

  public Person (string name) => Name = name;

  public string Name
  {
    get => name;
    set => name = value ?? "";
  }

  ~Person () => Console.WriteLine ("finalize");
}

Deconstructors

C# 7 introduces the deconstructor pattern. Whereas a constructor typically takes a set of values (as parameters) and assigns them to fields, a deconstructor does the reverse and assigns fields back to a set of variables. We could write a deconstructor for the Person class in the preceding example as follows (exception-handling aside):

public void Deconstruct (out string firstName, out string lastName)<br>
{<br>
  int spacePos = name.IndexOf (' ');<br>
  firstName = name.Substring (0, spacePos);<br>
  lastName = name.Substring (spacePos + 1);<br>
}

Deconstructors are called with the following special syntax:

var joe = new Person ("Joe Bloggs");<br>
var (first, last) = joe;          // Deconstruction<br>
Console.WriteLine (first);        // Joe<br>
Console.WriteLine (last);         // Bloggs

Tuples

Perhaps the most notable improvement to C# 7 is explicit tuple support (see “Tuples (C# 7)” in Chapter 4). Tuples provide a simple way to store a set of related values:

var bob = ("Bob", 23);
Console.WriteLine (bob.Item1); // Bob
Console.WriteLine (bob.Item2); // 23

C#’s new tuples are syntactic sugar for using the System.ValueTuple<…> generic structs. But thanks to compiler magic, tuple elements can be named:

var tuple = (Name:"Bob", Age:23);
Console.WriteLine (tuple.Name); // Bob
Console.WriteLine (tuple.Age); // 23

With tuples, functions can return multiple values without resorting to out parameters:

static (int row, int column) GetFilePosition() => (3, 10);

static void Main()
{
  var pos = GetFilePosition();
  Console.WriteLine (pos.row);      // 3
  Console.WriteLine (pos.column);   // 10
}

Tuples implicitly support the deconstruction pattern, so they can easily be deconstructed into individual variables. We can rewrite the preceding Main method so that the tuple returned by GetFilePosition is instead assigned to two local variables, row and column:

static void Main()
{
(int row, int column) = GetFilePosition(); // Creates 2 local variables
Console.WriteLine (row); // 3
Console.WriteLine (column); // 10
}

throw expressions

Prior to C# 7, throw was always a statement. Now it can also appear as an expression in expression-bodied functions:

public string Foo() => throw new NotImplementedException();

A throw expression can also appear in a ternary conditional expression:

string Capitalize (string value) =>
value == null ? throw new ArgumentException ("value") :
value == "" ? "" :
char.ToUpper (value[0]) + value.Substring (1);

Top career enhancing courses you can't miss

My Learning Resource

Excel your system design interview