Pages

C# Features Through the History - Version 7.0

The main features of version 7.0, available for you now!


The version 7.0 of the C# Language was released with Visual Studio 2017, following the version 4.7 and. We are in the version 1.1 of .Net Core.


How to use the examples 

The examples are available here. You can open the solution and play around. Just uncomment the code and have fun! 



Digit and Binary Separator

To improve the readability of the code, it was introduced this concept. This concept can be applied to decimal, float and double types too.
            //representing a binary value
            int binaryOldWay = 0b101010111100110111101111;
            int binaryNewWay = 0b1010_1011_1100_1101_1110_1111;

            //representing digits
            long billingsOfThings = 234_000_000_000;
            double doubleValue = 9.933_412_123_134;
            decimal decimalValue = 9.933_412_123_134M;
			  


Expanding the Expression Bodied Members

More members were added to the list of supported expression bodies approach. Now we have constructors, finalizers, get and set accessors on properties and indexers.
        // Expression-bodied constructor
        public ExpressionBodied(string label) => this.Label = label;

        // Expression-bodied finalizer
        ~ExpressionBodied() => Console.Error.WriteLine("Finalized!");

        private string label;

        // Expression-bodied get / set accessors.
        public string Label
        {
            get => label;
            set => this.label = value ?? "Default label";
        }
 

Local Functions

If you have a method that it's used for just one other method, you can use this approach as a matter of organizing more the code, creating a function inside another function, that will be used just from inside this one.
        //external function
        public static int Fibonacci(int value)
        {
            if (value < 0) throw new ArgumentException("Invalid value", nameof(value));
            
            return Fib(value).current;

            //local function defined
            (int current, int previous) Fib(int i)
            {
                if (i == 0) return (1, 0);

                var (p, pp) = Fib(i - 1);
                return (p + pp, p);
            }
        }    

Out variables

With this new version of the C# language, it's possible to create and use an output variable, without the necessity to create them before calling the method.
            //old way: separate the declaration of the out variable into two different statements
            int numericResult;
            if (int.TryParse(input, out numericResult))
                Console.WriteLine($"Old way: {numericResult}");
            else
                Console.Write("Could not parse input");

            //new way: declare variables in the argument list of a method call
            if (int.TryParse(input, out int numericResultNew))
                Console.WriteLine($"New Way: {numericResultNew}");
            else
                Console.Write("Could not parse input");

            //we can use implicit typed local variable
            if (int.TryParse(input, out var numericResultVar))
                Console.WriteLine($"New Way with Var: {numericResultNew}");
            else
                Console.Write("Could not parse input");

            Console.WriteLine($"Using outside local scope: {numericResultNew}");

Pattern Matching 

With pattern matching, it's possible to check whether an object is of a particular type and check its value in a concise way through the use of is patterns and case patterns.

The is pattern allows you to check the type of the variable, and assign it to a new one (in this case, the count variable):
            var input = 12;
            
            if (input is int count)
                Console.WriteLine(count);

It's possible to check for a null in an easier way:
            ClassTest classTest = null;

            if (classTest is null){
                classTest = new ClassTest();
                Console.WriteLine("ClassTest is not null anymore");
            }

You can use the case pattern to check the type:
            //case pattern, with input of type int = 12
            switch (input)
            {
                case int n when n > 100:
                    Console.WriteLine("Value of n is more than 100.");
                    break;

                case int n when n <= 100:
                    Console.WriteLine("Value of n is less than 100.");
                    break;

                default:
                    Console.WriteLine("value not found");
                    break;
            }  

Ref returns and locals 

Instead of only passing values by reference, now it's possible to return them by reference. In this example, we should use the ref keyword in some parts of the code, otherwise the value will not be stored:
        public static void ExecuteExample()
        {
            var items = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

            var itemInLocation = FindItemInArray(3, items);
            itemInLocation = 18;

            //[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
            Console.WriteLine("[{0}]", string.Join(", ", items));

            ref int itemInLocation2 = ref FindItemInArray(3, items);
            itemInLocation2 = 18;

            //the array is now: [1, 2, 18, 4, 5, 6, 7, 8, 9, 0]
            Console.WriteLine("[{0}]", string.Join(", ", items));
        }

        public static ref int FindItemInArray(int number, int[] numbers)
        {
            for (int i = 0; i < numbers.Length; i++)
            {
                if (numbers[i] == number) 
                {
                    //storage location
                    return ref numbers[i];
                }
            }

            throw new IndexOutOfRangeException($"{nameof(number)} not found");
        }

Tuples 

Tuples are lightweight data structures that contain multiple fields to represent the data members. Instead of using out parameters, System.Tuples, an anonymous type with dynamic return, you can use this structure in an easier way now.

Until before version 7.0, and element in a tuple could only be referenced as Item1, Item2 and so on. Now, it's possible to enables semantic names for the fields of a tuple using new, more efficient tuple types.

It's possible to define a tuple return from a method, in a simpler way:
        static (string, int, bool) FindAuthor() {
            
            var firstName = "John";
            var age = 32;
            var alive = true;

            //tuple literal
            return (firstName, age, alive); 
        }
		
		var author = FindAuthor();
		WriteLine($"Name: {author.Item1} - Age: {author.Item2} - Alive: {author.Item3}");            


You can call the same method, but now name each item of the returned tuple:
            (string name, int age, bool isAlive) author2 = FindAuthor();
            WriteLine($"Name: {author2.name} - Age: {author2.age} - Alive: {author2.isAlive}");            

And even more. It's possible to name the tuples directly in the method:
        static (string name, int age, bool isAlive) FindAuthor3() {
            
            var firstName = "John";
            var age = 32;
            var alive = true;

            return (firstName, age, alive); 
        } 
		
		var author3 = FindAuthor3();
		WriteLine($"Name: {author3.name} - Age: {author3.age} - Alive: {author3.isAlive}");            		


ValueTask Structure 

The Task<T> causes unnecessary overhead or allocation when you have a result that is immediately available.

The ValueTask<T> on the other hand, is a structure and has been introduced to prevent the allocation of a Task object in case the result of the async operation is already available at the time of awaiting.

Here is an example about heap allocation, that will be performed better using ValueTask:
        //the heap allocation is know synchronously (without Task.Delay)
        //JIT is less and using Task
        public async ValueTask TestTask()
        {
            await Task.Delay(100);
            return 5;
        }

And here we can see the flexibility cause by using this new structure:
    interface IAuthor
    {
        ValueTask GetAddressAsync();
    }

    class SynchronousAuthor : IAuthor
    {
        public ValueTask GetAddressAsync()
        {
            var value = default(T);
            return new ValueTask(value);
        }
    }

    class AsynchronousAuthor : IAuthor
    {
        public async ValueTask GetAddressAsync()
        {
            var value = default(T);
            await Task.Delay(1);
            return value;
        }
    }
	
	public async void GetAuthor()
	{
		//syncrhonous implementation for ValueTask
		var syncAuthor = new SynchronousAuthor();
		var result = syncAuthor.GetAddressAsync();            

		//asynchronous implementation for ValueTask
		var asyncAuthor = new AsynchronousAuthor();
		var result2 = await asyncAuthor.GetAddressAsync();            
	}	

Throw Expressions 

Now it's pretty easy to throw and exception in the middle of an expression.
	var customer = new Customer();

	var phone = customer.Phone ?? 
		throw new ArgumentNullException();

---------------------------------------

I hope you have enjoyed this journey until now. Have a nice week!

Here is the entire list of this series:


Fabio Ono

No comments:

Post a Comment