Introducing .NET 6 and C# 10

Spread the love

Last year we took a look at some of the new features in .NET 5 through WPF, this year I decided to keep it simpler. The idea is to get the new simpler, cleaner, code in our projects for the future.

We’ll look at some of the new features for our future projects togethers in a simple manner. The intention is to offer understanding the new features quickly, then when you build projects using these you know what you can do.

Init Data

For starters we’ll take a look at creating our data types, for instance models, you’d need for your application.

namespace ConsoleApp;
internal record class Person
{
    public string Name { get; init; } = "";
    public Car Vehicle { get; init; } = new Car();
}

For starters with .Net 6 and C# 10, we no longer will need the {} to wrap the files namespace. We can declare it in a single line for simpler layout.

Second using the record data type from the past you can note the simplified expression. We can give ‘default values‘ with ease:

internal record class Car
{
    public string Make { get; init; } = "BMW";
    public string Model { get; init; } = "320I";
}

Car car = new() { Model = "321J" };
// Will be a BMW 321J instead of 320I

This can be useful for complex data structures with default values. We can use records to easily manage our data even more.

Global Using

global using ConsoleApp;

We can create Global Using statements which add that using declaration for access everywhere in our project. This means I can create any file and use what’s accessible through ConsoleApp as if it’s listed in using there.

This means you can have your main classes for the project accessible everywhere.

Namespaces Simplified

We no longer need:

namespace ConsoleApp
{
  ...
}

We can now declare the namespace for our file, we no longer need ‘as many‘ indents.

namespace ConsoleApp;

Simpler Code

Instead of using the code:

    List<Person> Sample = new List<Person>();

    Sample.AddRange(new Person[] {
       new Person(){ Name = "Jane Soap" },
       new Person(){ Name = "Joe Soap" },
       new Person(){ Name = "Jane Doe" },
       new Person(){ Name = "Bob Doe" },
       new Person()
    });

We can simplify it way more for our readability as developers, this makes it easier to use and adjust.

    var Sample = new List<Person>();

    Sample.AddRange(new Person[] {
       new (){ Name = "Jane Soap" },
       new (){ Name = "Joe Soap" },
       new (){ Name = "Jane Doe" },
       new (){ Name = "Bob Doe" },
       new ()
    });

Pattern Matching Objects

    List<Person> Sample = new()
    {
        new() { Name = "Jane Doe", Vehicle = new() { Make = "Volkswagen", Model = "Golf" } },
        new() { Name = "Jane Doe", Vehicle = new() { Model = "Skyliner" } },
        new() { Name = "Jane Doe", Vehicle = new() { Model = "Roadliner" } }
    };

Take note of the Sample above. We used to use the following for our if statements:

        if (person is { Vehicle: { Make: "BMW" } })

We can now simplify it a little:

        if (person is { Vehicle.Make: "BMW" }) // Pattern Matching instead of (person is { Vehicle: { Make: "BMW" } }) style from past
        {
            Console.WriteLine($"There's a {person.Name} who owns a {person.Vehicle.Make}, it's a {person.Vehicle.Model}");
        }

Complex Constants

When you have a name you need to put into multiple constants you can now put the part that might change into it’s own constant.

    // Note: 'test' must be 'const'
    const string test = "Test";
    const string expected = $"{test} succeeded";

More Variable Control

We can manage our variables in easier ways, we can start using more Lambda expressions for ourselves.

    // 1. Move variable controls
    // Variables A
    (int x, int y) = (0, 1);
    Console.WriteLine("x:{0}, y:{1}", x, y);
    // Variables B
    int z;
    (z, int a) = (x, y);
    Console.WriteLine("z:{0}, a:{1}", z, a);
    // Variables C
    var point = (x - 1, y - 1);
    (z, a) = point;
    Console.WriteLine("z:{0}, a:{1}", z, a);
    // Variable D
    var a3Dpoint = (x, y, z);
    (z, y, x) = a3Dpoint;
    Console.WriteLine("{0}, from x:{1}, y:{2}, z:{3}", a3Dpoint, x, y, z);

We can also use this to our advantage for Func:

    // Function takes in an 'int' structured variable, and outputs a 'string' structured variable
    Func<(int, int, int), (string, string, string)> func = (input) =>
     {
         return
         ($"Added: {input.Item1 + input.Item2 + input.Item3}",
          $"Subtracted: {input.Item1 - input.Item2 - input.Item3}",
          $"Multiple with Divide: {(input.Item1 * input.Item2) / input.Item3}");
     };

Note, we could have input as (int, bool, string) for the Func. We can also have different sized output. Consider the above just does a simple calculation.

// One complex object in memory, for one complex object result
var answer = func((10, 2, 3));
Console.WriteLine($"{answer.Item2}\r\n{answer.Item3}\r\n{answer.Item1}\r\n");

An example that would only take input:

    //Note: output 'int' was needed for 'input' to have structure
    Func<(int, string, bool, Array), int> func2 = (input) =>
    {
        if (input.Item3)
            Console.WriteLine("{1}: {0} {2}", input.Item1, input.Item2, input.Item4);
        return 0;
    };

Thoughts

This has introduced lovely simpler ways we can manage the data our code processes for us. We can make layouts way simpler for ourselves.

Full Code of Program.cs


Console.WriteLine("Press Number: 1 \"Person Search\"; 2 \"Extended Property\"; 3 \"Lambda Expressions\"; 4 \"Complex Constants\"; 5 \"Other\"");
var input = Console.ReadKey();

if (input.Key == ConsoleKey.D1)
{
    Console.WriteLine("\r\nPerson Search");
    Thread.Sleep(2000);

    List<Person> Sample = new List<Person>();

    Sample.AddRange(new Person[] {
       new (){ Name = "Jane Soap" },
       new (){ Name = "Joe Soap" },
       new (){ Name = "Jane Doe" },
       new (){ Name = "Bob Doe" },
       new ()
    });
    Console.Write("Enter search: ");
    var search = Console.ReadLine();

    Console.WriteLine();

    var path = "./out.txt";
    if (File.Exists(path)) File.Delete(path);
    using (var sw = new StreamWriter(path))
    {
        (from person in Sample
         where person.Name.ToLower().Contains(search.ToLower())
         select person)
        .ToList()
        .ForEach((person) =>
        {
            sw.Write($"\tThe chosen name is {person.Name}, of length {{{person.Name.Length}}}\r\n");
        });
    }
}
else if (input.Key == ConsoleKey.D2)
{
    Console.WriteLine("\r\nExtended Property");
    Thread.Sleep(2000);

    // C# 10
    // Names arent related, rather arbitrary here
    List<Person> Sample = new()
    {
        new() { Name = "Jane Doe", Vehicle = new() { Make = "Volkswagen", Model = "Golf" } },
        new() { Name = "Jane Doe", Vehicle = new() { Model = "Skyliner" } },
        new() { Name = "Jane Doe", Vehicle = new() { Model = "Roadliner" } }
    };

    Console.WriteLine("Input Full Name: Jane Doe\r\n");

    Sample.ForEach((person) =>
    {
        if (person is { Vehicle.Make: "BMW" }) // Pattern Matching instead of (person is { Vehicle: { Make: "BMW" } }) style from past
        {
            Console.WriteLine($"There's a {person.Name} who owns a {person.Vehicle.Make}, it's a {person.Vehicle.Model}");
        }
    });
}
else if (input.Key == ConsoleKey.D3)
{
    Console.WriteLine("\r\nLambda Expressions");
    Thread.Sleep(2000);

    Func<(int, int, int), (string, string, string)> func = (input) =>
     {
         return
         ($"Added: {input.Item1 + input.Item2 + input.Item3}",
          $"Subtracted: {input.Item1 - input.Item2 - input.Item3}",
          $"Multiple with Divide: {(input.Item1 * input.Item2) / input.Item3}");
     };
    var answer = func((10, 2, 3));
    Console.WriteLine($"{answer.Item2}\r\n{answer.Item3}\r\n{answer.Item1}\r\n");

    //Note: output 'int' was needed for 'input' to have structure
    Func<(int, string, bool, Array), int> func2 = (input) =>
    {
        if (input.Item3)
            Console.WriteLine("{1}: {0} {2}", input.Item1, input.Item2, input.Item4);
        return 0;
    };

    // Note: integer division!
    // Read more: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions#natural-type-for-lambda-expressions
}
// Note: Multi-platform App UI, .NET MAUI
// Future, read more: https://devblogs.microsoft.com/dotnet/update-on-dotnet-maui/
else if (input.Key == ConsoleKey.D4)
{
    Console.WriteLine("\r\nComplex Constants");
    Thread.Sleep(2000);

    // Note: Must be 'const'
    const string test = "Test";
    const string expected = $"{test} suceeded";

    Console.WriteLine(expected);
}
else if (input.Key == ConsoleKey.D5)
{
    Console.WriteLine();
    // 1. Move variable controls
    // Variables A
    (int x, int y) = (0, 1);
    Console.WriteLine("x:{0}, y:{1}", x, y);
    // Variables B
    int z;
    (z, int a) = (x, y);
    Console.WriteLine("z:{0}, a:{1}", z, a);
    // Variables C
    var point = (x - 1, y - 1);
    (z, a) = point;
    Console.WriteLine("z:{0}, a:{1}", z, a);
    // Variable D
    var a3Dpoint = (x, y, z);
    (z, y, x) = a3Dpoint;
    Console.WriteLine("{0}, from x:{1}, y:{2}, z:{3}", a3Dpoint, x, y, z);

}