Learning C# by Example

What's new in C# 7.0, 7.1, 7.2, 7.3

  1. What is new in C# 7.0?:
    1. Smarter "out" variables

      "out" variables need not be declared. Also you can even use the "var" type if C# can infer type.

      private static void SimulateSolarSystem()
      {
      //Decimal planetMass; //before C#7.0 you had to declare variables,
      //Decimal moonMass; // now C# can infer types and create variables
      
      GenerateRandomMasses(out Decimal planetMass, out Decimal moonMass);
      Console.Out.WriteLine($"planetMass: {planetMass}, moonMass: {moonMass}.");
      }
    2. Tuples

      Although we can return multiple values from a method in C# by using the "out" parameter, custom types, System.Tuple types, or dynamic types, 7.0 brings us a tuple types and tuple literals. Tuples are public mutable fields. You can even deconstruct a tuple, like in Ruby. Inferred names will be added in 7.1.

      static void Main(string[] args)
      {
      var employee = GetEmployee(314159);
      Console.Out.WriteLine($"name: {employee.Item1} {employee.Item2}, {employee.Item3}");
      //since we named the variables in the function we can use their actual names
      Console.Out.WriteLine($"name: {employee.first} {employee.last}, {employee.age}");
      
      //and you can even deconstruct a tuple and put into separate variables.
      (string first, string last, int age) = GetEmployee(314159);
      Console.Out.WriteLine($"first: {first}");
      
      }
      static (string first, string last, int age) GetEmployee(int id)
      {
      return ("Ferdinand", "Magellan", 37);
      }
      
    3. Discards

      When deconstructing a tuple or invoking a method with "out" parameters, sometimes we don't care about the value returned. We can discard that value using the underscore character, "_".

      [Test]
       public void SumEvensOdds()
       {
           AddNumbers addNumbers = new AddNumbers();
           IEnumerable<object> numberList = new List<object>() { 1, 2, 3, 4, 5, 6, 7, 1.0, 2.0, 3.0 };
           //note "_" below is a discard - we don't care about that deconstructed tuple value
           (int sumEvenInt, int sumOddInt, _) = addNumbers.SumEvensOddsAndDoubles(numberList);
           Assert.AreEqual(12, sumEvenInt);
           Assert.AreEqual(16, sumOddInt);
           WriteLine($"sumEvenInt: {sumEvenInt}, sumOddInt: {sumOddInt}");
       }
      
    4. Local Functions

      Functions can now be declared inside other functions like in JavaScript. This keeps code a little cleaner in this contrived example:

      using System.Collections.Generic;
      using System.Linq;
      
      namespace LocalFunctions
      {
          public class Stats
          {
              public (int min, int max) GetStats(IEnumerable<int> list)
              {
                  int min = FindMin(list);
                  int max = FindMax(list);
                  return (min, max);
      
                  int FindMin(IEnumerable<int> list4min)
                  {
                      return list4min.Min();
                  }
                  int FindMax(IEnumerable<int> list4max)
                  {
                      return list4max.Max();
                  }
      
              }
          }
      }
      
      //and here's the Unit Test:
      
      using NUnit.Framework;
      using LocalFunctions;
      using System.Collections.Generic;
      using static System.Console;
      
      namespace LocalFunctionsTest
      {
          [TestFixture]
          public class StatsTest
          {
              [Test]
              public void LocalFunction()
              {
                  Stats stats = new Stats();
                  (int min, int max) = stats.GetStats( new List<int> { 1, 2, 3 });
                  WriteLine($"min: {min}, max: {max}");
                  Assert.AreEqual(1, min);
                  Assert.AreEqual(3, max);
                  (min, max) = stats.GetStats( new int[]{ 4, 5, 6 });
                  WriteLine($"min: {min}, max: {max}");
                  Assert.AreEqual(4, min);
                  Assert.AreEqual(6, max);
              }
          }
      }
      
      
    5. Pattern Matching

      In object-oriented programming method dispatch is based on the type of object, so when we invoke "speak()" on a sheep object it says "baa", and a cow will say "moo". Pattern Matching lets us be more flexible and allows us to do method dispatching based on our whims, not based on object hierarchy. Below is an example of doing actions based on the type of object using the "is" keyword and having C#7.0 do the variable creation and assignment for us.

      using System.Collections.Generic;
      
      namespace PatternMatching
      {
      public class AddNumbers
      {
          public (int sumInts, double sumDoubles) SumIntsAndDoubles(IEnumerable<object> numbers)
          {
              int sumInts = 0;
              double sumDoubles = 0.0;
      
              foreach(var number in numbers)
              {
                  if(number is int i) //tests for type, creates a variable, and assigns it
                  {
                      sumInts += i;
                  } else if(number is double d)
                  {
                      sumDoubles += d;
                  }
              }
              return (sumInts, sumDoubles);
          }
      }
      }
      

      Pattern Matching is also available in "case" clauses and can use the "when" syntax to have several cases with the same type as shown below. Note that the order of the case clauses is now significant.

      public (int sumEvenInts, int sumOddInts, double sumDoubles) SumEvensOddsAndDoubles(IEnumerable<object> numbers)
      {
           int sumEvenInts = 0;
           int sumOddInts = 0;
           double sumDoubles = 0.0;
      
           foreach (var n in numbers)
           {
               switch(n)
               {
                   case int evenNumber when evenNumber % 2 == 0:
                       sumEvenInts += evenNumber;
                       break;
                   case int oddNumber when oddNumber % 2 == 1:
                       sumOddInts += oddNumber;
                       break;
                   case double doubelNumber:
                       sumDoubles += doubelNumber;
                       break;
                   default:
                       throw new Exception("number was not int or double");
               }
           }
           return (sumEvenInts, sumOddInts, sumDoubles);
      }
      
    6. Literal update

      Literals can now have a digit separator "_" inside the numbers to improve readability. The underscore can go anywhere in the number. The compiler ignores them, the "_" is just there to make it more readable.

      var nationalDebt = 21_000_000_000_000M;
      
    7. Ref returns and locals

      Values returned by a method may now be returned by reference and stored as a local variable. This is useful in locating items in arrays and then modifying that item inside the array.

      public ref string GetNameByRef(string name, string[] names)
       {
           for (int i = 0; i < names.Length; i++)
           {
               if (names[i] == name)
               {
                   return ref names[i]; // return pointer to memory location, not the value
               }
           }
           throw new IndexOutOfRangeException($"{nameof(name)} was not found");
       }
      
    8. Generalized async return types

      Instead of async methods just returning void, Task or Task<T>, we can how return other thypes like ValueTask<T> for struct type. This is most usefull for frameworks, and not for everyday programming.

    9. More expression bodied members

      Expression bodied methods may now contains accessors, contructors and finalizers.

    10. Throw expressions

      Throw expressions may now be called as expressions.

      int x = 5;
      int y = x > 0 ? x : throw new Exception("nameof(x) not greater than 0.");
      
  2. What's new in C# 7.1? : Async Main, Default Literal Expressions, Inferred Tuple Element Names, and Generic Pattern Matching.
    1. Async Main

      In addition to the traditional "Main" signatures,you can now use these Async ones:

      public static Task Main();
      public static Task<int> Main();
      public static Task Main(string[] args);
      public static Task<int> Main(string[] args);
      
    2. Default Literal Expressions

      Instead of using "default(type)", we can now just use "default" when the type can be inferred.

       
      int iAmADefaultInt = default(int); //old way
      bool iAmADefaultBool = default(bool);
      
      //C#7.1 makes it even easier with a literal expression when the type can be inferred
      iAmADefaultInt = default; //new 7.1 way
      iAmADefaultBool = default;
      
      Console.Out.WriteLine($"iAmADefaultInt={iAmADefaultInt}");
      Console.Out.WriteLine($"iAmADefaultBool={iAmADefaultBool}");
      
    3. Inferred Tuple Element Names

      Instead of using "Item1", "Item2" we can use the more natural name:

      string first = "John";
      string last = "Rolfe";
      var fullname = (first, last);
      string firstName = fullname.Item1;//old method
      firstName = fullname.first; //new method, much cleaner
      Console.Out.WriteLine(firstName);
      
    4. Generic Pattern Matching

      The "switch" statement can now use generically typed values:

      interface IAnimal { }
      interface IFood { }
      class Sheep : IAnimal { }
      class Cow : IAnimal { }
      void Feed<T>(T animal, IFood food) where T : IAnimal
          {
          switch (animal)
          {
          case Sheep sheep:
          // feed sheep
          break;
          case Cow cow:
          // feed cow
          break;
          }
          }
      
  3. What's new in C# 7.2?:
    1. Digital Separator after Base Specifier

      The "_" separator can now appear after the base Specifier

      var hex = 0xff_ff_ff;
      hex = 0x_ff_ff_ff;
      
    2. Non-trailing Named Arguments

      Positional arguments can now followed named arguments:

      ProcessImage(img, color: 32, true);
      
    3. Private Protected

      With the access modifiers "protected internal" a member may be accessed from subclasses, but only in the same assembly.

      protected internal string name;
      
    4. Ref Conditional Expression

      Reference variables can now be bound to a different expression.

    5. Reference Semantics for Value Types

      Passing structs to methods is great for performance. Now you can pass a struct and declare it as read-only using "in" to modify the variable being passed.

  4. What's new in C# 7.3?:

    7.3 has three themes: speed improvements to make safe code as fast as unsafe code, small improvements to existing features, and a two new compiler options.

    1. Safe Code Speed Improvements
      1. Fixed fields can be accessed without pinning
      2. "ref" local variables can be reassigned
      3. Initializers can be used on "stackalloc" arrays
      4. "fixed" statements can be used in more places
      5. New additional generic contraints
    2. Incremental Improvements
      1. Tuple types can now use "==" and "!="
      2. Expression variables can occur in more places
      3. Attributes can be attached to backing fields of auto-implemented properties
      4. Method resolution has been improved for arguments with "in"
      5. Resolution for overloaded methods has been improved, resulting in fewer ambiguous cases
    3. Compiler Options
      1. "publicsign" which enables Open Source Software (OSS) signing of the assemblies.
      2. "pathmap" which provides mapping for source directories.