Quantcast
Channel: Don't Code Tired
Viewing all 207 articles
Browse latest View live

ICYMI C# 8 New Features: Prevent Bugs with Static Local Functions

$
0
0

This is part 6 in a series of articles.

C# 7 introduced local functions that allow you to create functions “local” to another function or method. These local functions are implicitly private to the enclosing function and cannot be accessed by other methods/properties/etc.  in the class. You can read more about local functions in this previous article.

C# 8 introduced the ability to make local functions static. This can help prevent bugs when using local functions.

Take the following code:

[Fact]
public void NestedCs7()
{
    int a = 10;

    int result1 = Add(10, 5);

    Assert.Equal(15, result1); // True, pass

    int result2 = Add(15, 2);

    Assert.Equal(17, result2); // False, fail

    int Add(int num1, int num2)
    {
        return a + num2;
    }
}

In the preceding code, the Add function is a local function. However there is a bug in this Add method, we are accidently referencing the variable a from the enclosing method NestedCs7. This means that instead of adding num1 and num2 and returning the result, we are adding a and num2. This will cause errors in the application.

Sometimes it may be convenient to reference (“capture”) the enclosing methods variables but to be more explicit and prevent unintended side-effects and bugs, from C# 8 you can make the method static.

In the following code we make the Add method static:

[Fact]
public void NestedCs8()
{
    int a = 10;

    int result1 = Add(10, 5);

    Assert.Equal(15, result1); 

    int result2 = Add(15, 2);

    Assert.Equal(17, result2); 

    static int Add(int num1, int num2)
    {
        return a + num2;
    }
}

If we try and build the preceding code, we’ll get a compilation error: Error CS8421    A static local function cannot contain a reference to 'a'.

This error tells us we’ve accidentally captured a and we can go and fix the bug:

[Fact]
public void NestedCs8()
{
    int a = 10;

    int result1 = Add(10, 5);

    Assert.Equal(15, result1); // True, pass

    int result2 = Add(15, 2);

    Assert.Equal(17, result2); // True, pass

    static int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.


ICYMI C# 8 New Features: Upgrade Interfaces Without Breaking Existing Code

$
0
0

This is part 7 in a series of articles.

Prior to C# 8, if you add members to an interface, exiting code breaks if you do not implement the new members in any class that implements the interface.

As an example, consider the following interface definition:

public interface ICustomer
{
    int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    // etc.
}

We could implement this interface:

class Customer : ICustomer
{
    public int Id { get; }
    public string Name { get; set; }
    public int MonthsAsACustomer { get; set; }
    public decimal TotalValueOfAllOrders { get; set; }
    public Customer(int id) => Id = id;
}

This will compile without error. We could have multiple classes implementing this interface in the same project or across multiple projects.

What happens now if we wanted to add a new interface method that represents the ability to calculate a discount based on the customer’s previous order value and how long they have been a customer?

We could make the following change:

public interface ICustomer
{
    int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    decimal CalculateLoyaltyDiscount();
    // etc.
}

Now if we try and build, we’ll get the error: 'Customer' does not implement interface member 'ICustomer.CalculateLoyaltyDiscount()'

If we have multiple implementations of ICustomer, they will all break.

Default Interface Methods in C# 8

From C# 8 we can fix this problem by providing a default implementation of an interface method.

If we only had one implementation of ICustomer then we could go and add the implementation of CalculateLoyaltyDiscount. But if we had multiple implementations or we didn’t want to force a breaking change on existing implementers then we can actually add the implementation of the the method in the interface itself.

public interface ICustomer
{
    int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    public decimal CalculateLoyaltyDiscount()
    {
        if (MonthsAsACustomer > 24 || TotalValueOfAllOrders > 10_000)
        {
            return 0.05M;
        }

        return 0;
    }
    // etc.
}

If we build now there will be no error, even though we haven’t implemented the method in Customer. Customer ‘inherits’ the default implementation.

Notice in the preceding code that from C# 8, access modifiers are now allowed on interface members.

We could make use of this new method:

var c = new Customer(42)
{
    MonthsAsACustomer = 100
};

decimal discount = ((ICustomer)c).CalculateLoyaltyDiscount();

Notice in the preceding code that we have to cast Customer to ICustomer to be able to call CalculateLoyaltyDiscount– that’s because the method implementation is in the interface, not the Customer class.

The Customer class can still implement it’s own version of the CalculateLoyaltyDiscount method if the default implementation is not acceptable:

class Customer : ICustomer
{
    public int Id { get; }
    public string Name { get; set; }
    public int MonthsAsACustomer { get; set; }
    public decimal TotalValueOfAllOrders { get; set; }
    public Customer(int id) => Id = id;
    public decimal CalculateLoyaltyDiscount()
    {
        if (TotalValueOfAllOrders > 1_000_000)
        {
            return 0.1M;
        }

        return 0;
    }
}

We could refactor the upgraded interface to allow implementers to still be able to access the default implementation:

public interface ICustomer
{
    public int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    public decimal CalculateLoyaltyDiscount() => CalculateDefaultLoyaltyDiscount(this);
    protected static decimal CalculateDefaultLoyaltyDiscount(ICustomer customer)
    {
        if (customer.MonthsAsACustomer > 24 || customer.TotalValueOfAllOrders > 10_000)
        {
            return 0.1M;
        }

        return 0;
    }
    // etc.
}

And then in Customer:

class Customer : ICustomer
{
    public int Id { get; }
    public string Name { get; set; }
    public int MonthsAsACustomer { get; set; }
    public decimal TotalValueOfAllOrders { get; set; }
    public Customer(int id) => Id = id;
    public decimal CalculateLoyaltyDiscount()
    {
        if (TotalValueOfAllOrders > 1_000_000)
        {
            return 0.2M;
        }

        return ICustomer.CalculateDefaultLoyaltyDiscount(this);
    }
}

From C# 8, interfaces can also now have static fields, methods, and properties, e.g.:

private static int MonthsAsACustomerThreshold = 24;
public static int MonthsThreshold
{
    get => MonthsAsACustomerThreshold;
    set => MonthsAsACustomerThreshold = value;
}

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

ICYMI C# 8 New Features: Asynchronous Streams

$
0
0

This is part 8 in a series of articles.

In earlier versions of C# you could return an IEnumerable<T> from a method, for example to be consumed by a foreach loop.

The following example shows a method from a WPF app that returns 3 web pages as string content:

public static IEnumerable<string> LoadWebPages()
{
    using (var client = new HttpClient())
    {
        yield return client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Switch-Expressions").Result;
        yield return client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Write-Less-Code-with-Using-Declarations").Result;
        yield return client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Write-Less-Code-with-Using-Declarations").Result;
    }
}

This method could be used in a click event handler in the WPF app (or via MVVM etc.):

private void NotAsync_Click(object sender, RoutedEventArgs e)
{
    foreach (var page in WebPageLoader.LoadWebPages())
    {
        var titleIndex = page.IndexOf("<title>");
        txt.Text += Environment.NewLine + $"Got page: {page.Substring(titleIndex, 110)}";
    }
}

When you run the app and click the button, the 3 web pages will be loaded and added to the content of the <TextBlock x:Name="txt"></TextBlock>

While loading the 3 web pages and looping through the foreach loop however, the app will be unresponsive until all 3 pages have been returned in the foreach loop.

C# 8 introduced the ability to use the IAsyncEnumerable<T> interface to iterate items asynchronously.

The “asynchronous streams” feature of C# 8 should not be confused with the streams in the System.IO namespace.

The LoadWebPages method can be re-written in C# 8 as:

public static async IAsyncEnumerable<string> LoadWebPagesAsync()
{
    using var client = new HttpClient();

    yield return await client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Switch-Expressions");
    yield return await client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Write-Less-Code-with-Using-Declarations");
    yield return await client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Simplify-If-Statements-with-Property-Pattern-Matching");
}

And to consume this version:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    await foreach (var page in WebPageLoader.LoadWebPagesAsync())
    {
        var titleIndex = page.IndexOf("<title>");
        txt.Text += Environment.NewLine + $"Got page: {page.Substring(titleIndex, 110)}";
    }
}

Now when the <Button Content="Async" x:Name="Async" Click="Async_Click"></Button> button is clicked, the 3 webpages will be enumerated in an async manner meaning that the UI will remain responsive.

The IAsyncEnumerable<T> interface also provides for cancellation: IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

Exception Handling in C# Course Update

$
0
0

An updated version of my Exception Handling in C# course is now available that updates demos to to use .NET 5.

From the course description: “At the core of handling errors in C# code is a thorough knowledge of exception handling. In this course, Error Handling in C# with Exceptions, you’ll learn how to write code that can detect and respond to runtime errors. First, you’ll learn why exceptions are used to represent errors in C# and how they are organized into class hierarchies. Next, you’ll explore how to throw, catch, filter, rethrow, and wrap exceptions. Finally, you’ll discover how to define, throw, and catch your own customized exception classes and also write unit tests for exception throwing code. When you’re finished with this course, you’ll have a thorough knowledge of C# exceptions that will help you to create production-ready C# applications that detect and respond to runtime errors.”

You can start watching the course with a free trial.

Watch All My Courses For Free This April

Free .NET Testing Courses This Month

$
0
0

This month all my Pluralsight courses are available for free including a lot of .NET testing content that can help you either get started with .NET testing or level-up your tests to make them easier to read and more maintainable.

Suggested Courses

Step 1: Learn a base testing framework:

Step 2: Level-up the base testing framework:

Step 3: Power-ups to complete your test strategy

Bonus Step: Help convince your co-workers and managers to let you write tests with the Testing Automation: The Big Picture course.

New Course Update: Working with Files and Streams in C#

$
0
0

An newly updated version of my Working with Files and Streams in C# video training course is now available. This version includes updated demos to .NET 5.

“C# has so many different ways to work with the file system and read and write data. It can be difficult to know what the best approach is and where to start. This course will teach you how to manipulates files, directories, paths, and streams in C#.”

Start watching with a free trial today.

ICYMI C# 9 New Features: Top-level Statements

$
0
0

This is the first in a series of articles on new features introduced in C#9.

Top-level statements allow you to simplify and remove some of the “ceremony” in your code.

For example, take the following console application written in C#8:

using System.Linq;
using static System.Console;

namespace ConsoleAppCS8
{
    class Program
    {
        static int Main(string[] args)
        {
            string greeting = "";

            if (args.Any())
            {
                greeting = args[0];
            }

            WriteLine("Please enter your name");
            var name = ReadLine();

            var upperName = ConvertToUpper(name);

            WriteLine($"{greeting} {upperName}");

            return 42;
        }

        public static object ConvertToUpper(string name)
        {
            return name.ToUpperInvariant();
        }
    }
}

In the preceding code the “ceremony” consists of things such as the enclosing namespace, the Program class outline, and the Main method itself.

With top-level statements in C# 9 this code can be simplified to the following:

using System.Linq;
using static System.Console;


string greeting = "";

if (args.Any())
{
    greeting = args[0];
}

WriteLine("Please enter your name");
var name = ReadLine();

var upperName = ConvertToUpper(name);

WriteLine($"{greeting} {upperName}");

return 42;


static object ConvertToUpper(string name)
{
return name.ToUpperInvariant();
}

Notice in the C# 9 version that the structure is a lot “flatter” because there are no nested {} from the namespace, class, and Main method.

The application will still act in the same way, there is just less boilerplate code.

Notice that in the top-level version you can still have methods e.g. ConvertToUpper(), you can still return a value return 42; and you can still access any command line arguments greeting = args[0];

Some Considerations

Whilst top-level statement make the application seem more “script like” this may or may not be a good thing from someone learning C# for the very first time, depending on if they have any other prior programming experience.

Behind the scenes, a synthesized entry point into the application is created for you. If you return a value, the synthesized entry point (a bit like the Main method in the C# 8 version) will have a return value and if you use any async code the synthesized entry point will return Task or Task<int>– essentially using top-level statements doesn’t restrict you from the kind of code you can write.

Notice in the C# 8 version the method public static object ConvertToUpper(string name) could be accessed by another class in the console app:

class Class1
{
    public void XYZ()
    {
        Program.ConvertToUpper("ddd");
    }
}

In the C# 9 top-level version the method becomes:

static object ConvertToUpper(string name)
{
  return name.ToUpperInvariant();
}

If we try to write the same Class1 in the C# 9 project we’ll get an error “Error    CS0103    The name 'Program' does not exist in the current context “. We could of course refactor the ConvertToUpper() method into an explicit static class to make it accessible

You can only have one top-level file in a project, and you can’t also have an explicit Main method entry point at the same time without getting a compiler warning.

A file with top level statements can also include namespaces/classes but they must be after the top-level statements.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.


ICYMI C# 9 New Features: More Pattern Matching Features

$
0
0

This is part of a series of articles on new features introduced in C# 9.

Pattern matching was introduced in earlier versions of C# and C# 9 continued to evolve this feature. Pattern matching in a more general sense allows you to write an expression and evaluate it to see whether or not it meets some specified characteristic or “pattern”. In some situations pattern matching can reduce the amount of code you need to write and improve readability.

Pattern matching is supported in is expressions, switch statements, and switch expressions.

C# 9 Type Pattern

The type pattern allows your code to perform a runtime check that an expression of a specified type. In C# 9, when using the type pattern you no longer need to assign to a variable or use a discard.

Prior to C# 9:

object o = "hello";
string message = o switch
{
    string _ => "it's a string",
    int _ => "it's an int",
    _ => "it's something else"
};

WriteLine(message);

ReadLine();

Notice in the preceding code the required discards (or variable assignments) _.

From C# 9 this can be simplified to:

object o = "hello";

string message = o switch
{
    string => "it's a string",
    int => "it's an int",
    _ => "it's something else"
};

WriteLine(message);

ReadLine();

Notice that now no discard is required.

Logical, Parenthesized, and Relational Patterns in C# 9

With C# 9 you can now create logical patterns using not, and, or.

Prior to C# 9 you had to write:

if (o != null)
{
    WriteLine(o);
}

From C# 9 you can write:

if (o is not null)
{
    WriteLine(o);
}

As another example, prior to C# 9 you could not write:

if (o is not string)
{
    WriteLine("not a string");
}

Examples of or, and:

string minuteHandDescription = minuteHandValue switch
{>=0 and <= 15 => "first quarter",>15 and <= 30 => "second quarter",>30 and <= 45 => "third quarter",
    (>45 and <=58) or 59 or 60 => "fourth quarter",
    _ => "unknown"
};

Also notice in the preceding code the use of the parenthesized pattern(>45 and <=58) or 59 or 60 to enforce precedence.

The code also makes use of the new C# 9 relational patterns  that include: <, >, <=, and >=.When using these “The right-hand part of a relational pattern must be a constant expression. The constant expression can be of an integer, floating-point, char, or enum type.” [Microsoft]

For more information on patterns check out the C# language reference.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

ICYMI C# 9 New Features: Reducing Code with Target-typed New Expressions

$
0
0

This is part of a series of articles on new features introduced in C# 9.

C# 9 introduced some enhancements to reduce the amount of code you need when creating new instances of objects.These target-typed new expressions “Do not require type specification for constructors when the type is known.” [MS]

As an example in C# 8 with fields you need to explicitly create an instance:

class Preferences
{
    private List<string> _favoriteColors = new List<string>();
}

From C# 9 you can instead write:

class Preferences
{
    private List<string> _favoriteColors = new();
}

Notice in the preceding code you can simply write new() because the target type is known to be List<string>.

If you are calling a method (or constructor) that requires an instance of an object, you can also use new(). For example suppose you had the following method that requires a DisplayOptions instance:

public void DisplayColors(DisplayOptions options)
{
    Console.WriteLine(options.Title);
    foreach (var color in _favoriteColors)
    {
        Console.WriteLine(color);
    }
}

Prior to C# 9, if you wanted to just create a new instance of DisplayOptions and pass it in you would write:

var prefs = new Preferences();            
prefs.DisplayColors(new DisplayOptions());

With C# 9 you can simplify this to:

var prefs = new Preferences();
prefs.DisplayColors(new());

You could also write this using a target-typed new expression for the Preferences instance:

Preferences prefs = new();
prefs.DisplayColors(new());

If you have init only properties you can also use target-typed new expressions:

class DisplayOptions
{
    public string Title { get; init; }
}
DisplayOptions options = new() { Title = "Colors" };

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

ICYMI C# 9 New Features: Adding foreach Support To Any Type

$
0
0

This is part of a series of articles on new features introduced in C# 9.

Prior to C# 9 you could add foreach support to a class by modifying the class and implementing the IEnumerable interface. With C# 9 you can add foreach support to an existing class without actually needing to modify that class.

As an example, suppose the following class exists in our codebase:

public class Choices
{
    public string FirstChoice { get; set; }
    public string SecondChoice { get; set; }
    public string ThirdChoice { get; set; }
    public string FourthChoice { get; set; }
    public string FifthChoice { get; set; }
}

Because the class is in our control we could add the ability to foreach through all the choices (FirstChoice, SecondChoice, etc.).

One way to do this would be to modify the Choices class and implement IEnumerable– we could also just create a method called GetEnumerator and then have it return a class which has a MoveNext and Current method:

public class ChoicesEnumerator
{
    public string[] _choices = new string[5];

    int position = -1;

    public ChoicesEnumerator(Choices choices)
    {
        // Additional null/validation/etc checking code omitted

        _choices[0] = choices.FirstChoice;
        _choices[1] = choices.SecondChoice;
        _choices[2] = choices.ThirdChoice;
        _choices[3] = choices.FourthChoice;
        _choices[4] = choices.FifthChoice;
    }

    public bool MoveNext()
    {
        position++;
        return position < _choices.Length;
    }

    public string Current
    {
        get
        {
            try
            {
                return _choices[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

We could then modify the Choices class and add the GetEnumerator method:

public class Choices
{
    public string FirstChoice { get; set; }
    public string SecondChoice { get; set; }
    public string ThirdChoice { get; set; }
    public string FourthChoice { get; set; }
    public string FifthChoice { get; set; }
    public ChoicesEnumerator GetEnumerator()
    {
        return new ChoicesEnumerator(this);
    }
}

Now we can iterate through the choices in a foreach loop:

var choices = new Choices
{
    FirstChoice = "Orange",
    SecondChoice = "Blue",
    ThirdChoice = "Green",
    FourthChoice = "Cyan",
    FifthChoice = "Grey"
};

foreach (var choice in choices)
{
    WriteLine(choice);
}

But what if we were not able to modify the Choices class – for example if it is from an external library? From C# 9 you can follow a similar approach to the above but add the foreach support using an extension method. This way we don’t have to modify the original class.

If the original class that we couldn’t modify looked like the following:

public sealed class Choices
{
    public string FirstChoice { get; set; }
    public string SecondChoice { get; set; }
    public string ThirdChoice { get; set; }
    public string FourthChoice { get; set; }
    public string FifthChoice { get; set; }
}

We could once again create the ChoicesEnumerator class as above and then just create an extension method that adds the GetEnumerator method to the Choices class, without needing to modify the Choices class itself:

public static class ChoicesExtensions
{
    public static ChoicesEnumerator GetEnumerator(this Choices choices)
    {
        return new ChoicesEnumerator(choices);
    }
}

Now we can once again foreach through all the choices.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

ICYMI C# 9 New Features: Reduce Boilerplate Constructor Code with Init Only Property Setters

$
0
0

This is part of a series of articles on new features introduced in C# 9.

Prior to C# 9 if you wanted to create properties that can be set only when the object is created, you could make  the property setter private and use constructor arguments to set them:

class PaymentV1
{
    public Guid Id { get; private set; }
    public decimal Value { get; private set; }
    public string Notes { get; set; }
    public PaymentV1(Guid id, decimal value)
    {
        Id = id;
        Value = value;
    }
}

In the preceding code, the Id and Value properties can only be set when the object is created by supplying constructor parameters. Once the Payment has been created you can’t set the Id or Value properties from outside the object instance.

In the preceding code, we have to add the extra constructor code just to create the “externally immutable” properties (they can still be set from code inside the class).

C# 9 introduces the concept of init only setters. These allow you to create immutable properties without needing to write the extra constructor boilerplate code:

class PaymentV2
{
    public Guid Id { get; init; }
    public decimal Value { get; init; }
    public string Notes { get; set; }
}

Notice in the preceding code that the Id and Value properties now use the init keyword instead of the set keyword. Also notice that we no longer need to create a constructor.

To set these immutable properties you need to do so at the time of object construction/creation by using the existing C# property initialization syntax, for example:

var payment2 = new PaymentV2
{
    Id = Guid.NewGuid(),
    Value = 45.50m,
    Notes = "Initial send on Friday."
};

Once this code executes and the payment2 object is created, you will not be able to set Id or Value:

payment2.Id = Guid.NewGuid(); // ERROR - only settable in initializer
payment2.Value = 99.00m; // ERROR - only settable in initializer
payment2.Notes += " Second send on Sunday."; // OK

You can also set init only properties from the constructor of a derived type:

abstract class PaymentBase
{
    protected Guid Id { get; init; }
    protected decimal Value { get; init; }
}

class PaymentV3 : PaymentBase
{
    public string Notes { get; set; }

    public PaymentV3(Guid id, decimal value)
    {
        Id = id;
        Value = value;
    }
}

You could also use init only properties and set a default value if the property is not set at creation and also add validation logic:

class PaymentV4
{
    private readonly string _currencyCode = "USD";

    public string CurrencyCode
    {
        get
        {
            return _currencyCode;
        }

        init 
        {
            if (value is null)
            {
                throw new ArgumentNullException(nameof(CurrencyCode));
            }

            if (value.Length != 3)
            {
                throw new ArgumentOutOfRangeException(nameof(CurrencyCode), "Must be 3 long.");
            }

            // etc.
            _currencyCode = value;
        }
    }
}

With the preceding code, we could try the follow statements:

var payment4 = new PaymentV4(); // Default CurrencyCode of "USD"
var payment4 = new PaymentV4 { CurrencyCode = "AUD" };
var payment4 = new PaymentV4 { CurrencyCode = null }; // Exception
var payment4 = new PaymentV4 { CurrencyCode = "hello" }; // Exception

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

New Pluralsight Course Update: Working with Nulls in C#

$
0
0

A new version of my Working with Nulls in C# Pluralsight course was just released. This new version features updated demos in .NET 5 & C# 9 plus a brand new module has been added.

The course contains the following modules:

  • Working with Nullable Value Types and Strings
  • Accessing and Checking for Null Values
  • Eliminating Null Reference Exceptions with the Null Object Pattern
  • Understanding Nullable and Non-nullable Reference Types
  • Using Additional Attributes to Describe Nullability

From the course description: “Making use of an object when it is not supposed to be null can result in unexpected exceptions that can cause your program to crash. These unexpected null related errors may cause data loss or corruption, system downtime, and unhappy users. In this course, Working with Nulls in C#, you’ll gain the ability to reduce the likelihood of getting null related exceptions in production. First, you’ll explore which objects can be set to null and how to check for null values. Next, you’ll discover a design pattern to help eliminate null related exceptions. Finally, you’ll learn how to opt-in to the ability to create non-nullable reference types that enable the compiler to detect null-related problems before you even run your application. When you’re finished with this course, you’ll have the skills and knowledge of nulls in C# needed to reduce null related errors in your production code.”

You can start watching with a Pluralsight free trial.

ICYMI C# 9 New Features: Create Immutable Objects with Records

$
0
0

This is part of a series of articles on new features introduced in C# 9.

C# 9 introduced a new type of object that is neither a class or a struct. This new type is called a  record.

In C# 9 a record is a reference type that has value type equality semantics (more on this below).

The main purpose of defining record types is to indicate immutability for a type that is “data-centric” or in other words does not have rich behaviour (such as data transfer objects, database records, etc).

How to Define a Record in C# 9

To define a record type you use the record keyword:

record Message1
{
    public int Priority { get; set; }
    public string MessageBody { get; set; }
}

We could now create an instance and then write it to the console window:

var m1 = new Message1();
m1.Priority = 1;
m1.MessageBody = "Hi";

Console.WriteLine(m1);

This would produce the following output:

Message1 { Priority = 1, MessageBody = Hi }
Console.WriteLine automatically calls ToString() on the object passed to it, notice that we get built-in ToString() formatting support for all record types.

 

Notice in the preceding code that we are able to set Priority and MessageBody even after we have created the object – this is not immutable behaviour. To make a record immutable when declaring properties manually (see positional records below) you need to make the property setter init only:

record Message2
{
    public int Priority { get; init; }
    public string MessageBody { get; init; }
}

Now if you try and write the following code you’ll get a compiler error (“Init-only property or indexer … can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor”):

var m2 = new Message2();
m2.Priority = 2;
m2.MessageBody = "Hey there!";

To create Message2 instances you now need to set the properties when you create it, for example:

var m2 = new Message2()
{
    Priority = 2,
    MessageBody = "Hey there!"
};
You can also add constructors to record types if you want to.

What Are Positional Records in C#?

Positional records are a shorthand syntax for defining C# records. Behind the scenes they create init-only properties.

We could define a message class that is essentially the same as Message2 above with the following syntax:

record Message3(int Priority, string MessageBody);

Now we could create one with the following syntax:

var m3 = new Message3(3, "Good day sir!");

Or if you wanted to be explicit:

var m3 = new Message3(Priority: 3, MessageBody: "Good day sir!");

Even though behind the scenes we’re getting init-only properties, when you define a positional record you can’t use the following syntax:

var m3 = new Message3() // error missing arguments
{
    Priority = 3,
    MessageBody = "Good day sir!"
};

You can think of positional records as a shorthand syntax that creates init only properties and a parameterized constructor automatically behind the scenes.

Equality

Records have value-like equality semantics:

Record instances in C# 9 by default are considered equal if they store the same values and are of the same record type:

var m3a = new Message3(Priority: 3, MessageBody: "Good day sir!");
var m3b = new Message3(Priority: 3, MessageBody: "Good day sir!");
var m3c = new Message3(Priority: 3, MessageBody: "BOO!");

Console.WriteLine($"m3a == m3b : {m3a == m3b}"); // Outputs: TRUE
Console.WriteLine($"m3b == m3c : {m3b == m3c}"); // Outputs: FALSE

If you tried to compare a Message3 object with a Message2 object you’ll get a compiler error.

If you want to, you can override things like Object.Equals in a record.

Note: C# 10  will be introducing record structs .

Immutability of Record Types

One thing to be aware of is that immutability of records types is “shallow” for properties that are reference types.

In other words while you can’t change the value of a value type property you can change the properties of reference type properties in a record:

var m4 = new Message4(4, new[] { "Dear sir", "Good to see you.", "Good bye." });
Console.WriteLine(m4.MessageLines[0]); // OUTPUTS: Dear sir

m4.MessageLines[0] = "Yo yo!"; // NO COMPILER ERROR
Console.WriteLine(m4.MessageLines[0]); // OUTPUTS: Yo Yo!

m4.MessageLines = new[]; // ERROR MessageLines property object reference itself IS immutable

You can create a new immutable record object based on an existing immutable instance:

var one = new Message3(Priority: 3, MessageBody: "Good day sir!");
var two = one; 

Object two is a copy of one.

Note: record copies are “shallow” - any value type properties will have the value copied, but any reference type properties will only have the reference copied. That means that 2 record instances can have reference type properties that point to the same object. You change the object they point to and both records will be “updated” with (point to) the new value (because they share the reference to the same object in memory).

If a record is immutable, you can “update” it by creating a copy of it, and update some properties as required during the “copy” – you do this using the with keyword. For example to “update” the priority of an immutable record:

var priority3Message = new Message3(Priority: 3, MessageBody: "Good day sir!");
var priority1Message = priority3Message with { Priority = 1 };

As before, if you create a copy and use with a shallow copy is still created.

Custom C# Record Output Formatting

When you declare a record, under the hood a PrintMembers method is generated. You can also provide your own:

record Message5(int Priority, string[] MessageLines)
{
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        builder.Append($"P:{Priority}");
        for (int i = 0; i < MessageLines.Length; i++)
        {
            builder.Append($" {MessageLines[i]} ");
        }

        return true;
    }
}

Now the following code:

var m5 = new Message5(5, new[] { "Dear sir", "Good to see you.", "Good bye." });

Console.WriteLine(m5);

Would output:

Message5 { P:5 Dear sir  Good to see you.  Good bye.  }

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

What’s New in C# 10: Reclaim Horizontal Editing Space and Simplify Nesting

$
0
0

This is the first part of a series on the new features introduced with C# 10.

Prior to C# 10, to define types as being part of a specific namespace you would use the following syntax:

namespace ConsoleAppCS9
{
    public class SomeClass
    {
        public void SomeMethod(bool b, string s)
        {
            if (b is true)
            {
                if (s is not null)
                {
                    // do something
                }
            }
        }
    }
    namespace SomeNestedNamespace
    {
        public class AClassInANestedNamespace
        {
            // etc.
        }
    }
}


With C# 10 we can use something called file-scoped namespace declarations.

All this means is that we can remove one level of nesting in { } brackets:

namespace ConsoleAppCS9;

public class SomeClass
{
    public void SomeMethod(bool b, string s)
    {
        if (b is true)
        {
            if (s is not null)
            {
                // do something
            }
        }
    }
}

The line namespace ConsoleAppCS9; means that any types defined in that one file will be inside the ConsoleAppCS9 namespace.

This has the effect in the editor of giving us more horizontal editing space and nesting to keep mental track of.

Also note in the second example that you can’t have nested namespaces in the file if file-scoped namespace declarations are being used – in this case you’d have to return to the traditional namespace syntax.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.


What’s New in C# 10: Simplify Argument Null Checking Code

$
0
0

This is part of a series on the new features introduced with C# 10.

Prior to C# 10 you may have had code similar to the following:

public static string Join(string a, string b)
{            
    if (a is null)
    {
        throw new ArgumentNullException(nameof(a));
    }

    if (b is null)
    {
        throw new ArgumentNullException(nameof(b));
    }

    return a + b;
}

If the parameters a or b are null then an ArgumentNullException will be thrown.

The nameof operator will create a string from the parameter name so the exception will contain information about what parameter was null.

One potential problem with this code is that it is easier to reference the incorrect parameter, for example:

if (a is null)
{
    throw new ArgumentNullException(nameof(b));
}

The if statement is checking a but the nameof is referencing b.

C# 10 (.NET 6) introduces an improved way:

public static string Join(string a, string b)
{
    ArgumentNullException.ThrowIfNull(a);
    ArgumentNullException.ThrowIfNull(b);

    return a + b;
}

In the preceding code, a new static method called ThrowIfNull has been added on the ArgumentNullException class and allows us to quickly check and throw ArgumentNullExceptions.

Notice in this new version of the code we do not need to use the nameof operator, the parameter name will “magically” be worked out for us if an exception is thrown.

As an example, take the following code that passes a null to parameter a:

try
{
    SomeClass.Join(null, "bbbb");
}
catch (Exception ex)
{
    Console.WriteLine(ex);
}

If we run this we’ll get the following console output:

System.ArgumentNullException: Value cannot be null. (Parameter 'a')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at ConsoleApp1.SomeClass.Join(String a, String b)

Notice the message contains a reference to the parameter named ‘a’ automatically.(Behind the scenes this is due to the new [CallerArgumentExpression] attribute – but you don’t need to know about the implementation details to make use of the ThrowIfNull method.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

What’s New in C# 10: Write Less Code and Reduce Repeated Using Directives

$
0
0

This is part of a series on the new features introduced with C# 10.

There are 2 related features in C# 10 that will reduce the clutter of repetitive using directives: global using directives and implicit global using directives.

C# 10 Global Usings

At the top of every code file you will usually find a number of using directives:

using ConsoleApp1.Configuration;

namespace ConsoleApp1
{
    internal class Calculator
    {
        public int Add(int a, int b)
        {
            if (CalculatorConfiguration.SomeConfigProperty)
            {
                // etc.
            }
            return a + b;
        }
    }
}

In the preceding code there is a single using ConsoleApp1.Configuration; to get access to a CalculatorConfiguration class.

For one file this is not much overhead in terms of repeated code.

Imagine however if the CalculatorConfiguration class was referenced in 100’s of code files throughout the project. This would mean we’d have 100 using ConsoleApp1.Configuration; lines throughout the project.

As an alternative you can use the new global using declarations. To use these just prefix the directive with global, for example: global using ConsoleApp1.Configuration;

Now all code files in the project will act as if though they have a using ConsoleApp1.Configuration; at the top of them. You only need one global using directive for any given namespace in the project.

You can add global usings to any code file, however it makes sense to centralize them. For example you could create a GlobalUsings.cs file in the project and inside that just have all your global using directives.

C# 10 Implicit Global Usings

If you create a new C# 10 project (e.g. a console app) and open the project file you’ll see a line: <ImplicitUsings>enable</ImplicitUsings>

This enables the new implicit global usings feature.

When this feature is enabled, a number of commonly used namespaces will automatically have global usings directives added for them.

For a Console project the following will be automatically added:

// 
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;

This means for example you you could create a List<string> anywhere in the project without needing to add a using System.Collections.Generic; to the top of your code files.

Implicit global usings work behind the scenes by generating a file.

Assuming you had a console app called ConsoleApp1, you would find this generated file as follows: "ConsoleApp1\obj\Debug\net6.0\ConsoleApp1.GlobalUsings.g.cs".

If you opened this file you’d see the global usings that are implicitly added to your project.

If you don’t like the idea of implicit global usings you can opt out for new projects by making the following change in the project file: <ImplicitUsings>disable</ImplicitUsings>

What’s New in C# 10: New Possibilities for Validation and Logging Methods

$
0
0

This is part of a series on the new features introduced with C# 10.

From C# 10 we can make use of the [CallerArgumentExpression] attribute.

This attribute can be applied to a parameter to get information about another specified parameter in the method.

Take the following method as an example:

static bool ValidInput(string? inputFromUser,
                string inputDataName,
                bool validationCondition,
                out string? validationErrorMessage,
                [CallerArgumentExpression("validationCondition")] string? validationConditionText = null)
{
    if (validationCondition)
    {
        validationErrorMessage = null;
        return true;
    }

    validationErrorMessage = $"input '{inputFromUser ?? "null"}' from user for {inputDataName} is invalid because '{validationConditionText}'";
    return false;
}

In this method the validationConditionText argument has the [CallerArgumentExpression] applied.

When you use the [CallerArgumentExpression] attribute you need to supply a single constructor parameter. This is a string parameter that specifies which of the other parameters we want to capture information about. In this case it’s the bool validationCondition parameter.

We could make use of this method in a console application:

using System.Runtime.CompilerServices;
using static System.Console;

WriteLine("Please enter your user name");
string? userName = ReadLine();

WriteLine("Please enter your age");
string? age = ReadLine();

string? validationErrorMessage;

if (!ValidInput(userName,
                inputDataName: "user name",
                validationCondition: userName is not null,
                validationErrorMessage: out validationErrorMessage))
{
    WriteLine(validationErrorMessage);
}

if (!ValidInput(inputFromUser: age,
                inputDataName: "age",
                validationCondition: age is not null && int.TryParse(age, out _),
                validationErrorMessage: out validationErrorMessage))
{
    WriteLine(validationErrorMessage);
}

ReadLine();

Every time we call the ValidInput method, we pass a Boolean expression that needs to be satisfied for the input to be recognized as valid, for example: userName is not null.

If we ran the console app and entered a null for user name and some non numeric input for age:

Please enter your user name
^Z
Please enter your age
aaa
input 'null' from user for user name is invalid because 'userName is not null'
input 'aaa' from user for age is invalid because 'age is not null && int.TryParse(age, out _)'

Notice the two validation error messages output contain the boolean expression used in the source code: userName is not null and age is not null && int.TryParse(age, out _).

The [CallerArgumentExpression] attribute pulls out those expressions and lets us access them as strings to be used at runtime.

This kind of user validation is not the primary intended use case for this attribute as telling an end user 'age is not null && int.TryParse(age, out _)' is not very helpful or user friendly, however the example above illustrates the possibilities. This approach could still be used with a more generic error message given to the user and a more detailed one written to logs/traces. The Microsoft documentation states: “Diagnostic libraries may want to provide more details about the expressions passed to arguments. By providing the expression that triggered the diagnostic, in addition to the parameter name, developers have more details about the condition that triggered the diagnostic. That extra information makes it easier to fix.”

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

What’s New in C# 10: Easier Lambda Expressions

$
0
0

This is part of a series on the new features introduced with C# 10.

Prior to C# 10, working with lambda expressions required a bit more code to be written, for example to explicitly define the delegate type such as Action<T> or Func<T>:

// C# 9
Action writeWithColor = (string s, ConsoleColor color) =>
{
    var originalColor = Console.ForegroundColor;
    Console.ForegroundColor = color;
    Console.WriteLine(s);
    Console.ForegroundColor = originalColor;
};

Func upper = (string s) => s.ToUpperInvariant();

writeWithColor("Hello", ConsoleColor.Cyan);
Console.WriteLine(upper("This should be default color"));
writeWithColor("Bye", ConsoleColor.Yellow);

Console.ReadLine();

Notice in the preceding code the lambda statement writeWithColor and the lambda expression upper both need explicit delegate types: Action<string, ConsoleColor> and Func<string, string>

From C# 10 we can make use of the new feature of “natural” lambda expression types.

This “natural type” is inferred by the compiler when it can, this means in C# we could just use var: var writeWithColor = (string s, ConsoleColor color) => etc. and var upper = (string s) => s.ToUpperInvariant();

This natural type inference will not always be possible, for example when you haven’t defined lambda parameter types like: var upper = (s) => s.ToUpperInvariant(); If you tried to compile this line of code you would get: Error    CS8917    The delegate type could not be inferred.

From C# 10, you can specify an explicit return type for a lambda expression where the compiler can’t work it out for you. You add the return type before the lambda parenthesis:

//Error CS8917 The delegate type could not be inferred
var createException = (bool b) => b ? new ArgumentNullException() : new DivideByZeroException();

// No error
var createException = Exception (bool b) => b ? new ArgumentNullException() : new DivideByZeroException();

You can also sometimes benefit from natural types for method groups:

// C#9
Func getUserInput = Console.ReadLine;
Action tellUser = (string s) => Console.WriteLine(s);
Func waitForEnter = Console.ReadLine;

tellUser("Please enter name");
var name = getUserInput();
tellUser($"Your name is {name}");
waitForEnter();

From C# 10 we could just use var:

// C#10
var getUserInput = Console.ReadLine;
var tellUser = (string s) => Console.WriteLine(s);
var waitForEnter = Console.ReadLine;

tellUser("Please enter name");
var name = getUserInput();
tellUser($"Your name is {name}");
waitForEnter();

You can’t however write: var write = Console.Write; because the Write method has multiple overloads so the compiler doesn’t know which one to choose.

What’s New in C# 10: Take Control of Interpolated String Handling

$
0
0

This is part of a series on the new features introduced with C# 10.

In C# you can create an interpolated string such as: $"{DateTime.Now}: starting..."

The compiler will transform this to a single string instance using a call to String.Format or String.Concat.

Starting with C# 10 you can override this behaviour if you want more control such as:

  • Not interpolating the sting for performance reasons if it won’t be used
  • Limiting the length of resulting interpolated strings
  • Enforcing custom formatting of interpolated strings
  • Etc.

Take the following simple logging class:

// Simplified implementation
public static class SimpleConsoleLogger
{
    public static bool IsLoggingEnabled { get; set; }

    public static void Log(string message)
    {
        if (IsLoggingEnabled)
        {
            Console.WriteLine(message);
        }            
    }
}

We could call this as follows:

SimpleConsoleLogger.IsLoggingEnabled = true;
SimpleConsoleLogger.Log($"{DateTime.Now}: starting...");
SimpleConsoleLogger.IsLoggingEnabled = false;
SimpleConsoleLogger.Log($"{DateTime.Now}: ending...");

The second call (SimpleConsoleLogger.Log($"{DateTime.Now}: ending...");) won’t output a log message because IsLoggingEnabled is false, however the interpolation of the string $"{DateTime.Now}: ending..." will still take place.

Ideally if logging is not enabled we would not even want to bother interpolating the string. This could improve the performance of the application if logging was identified as a problem.

We can do this by taking control of when (or if) an interpolated string is processed by:

  • Applying the System.Runtime.CompilerServices.InterpolatedStringHandler attribute to a custom handler
  • Creating a constructor with int parameters: (int literalLength, int formattedCount)
  • Adding a public AppendLiteral method
  • Adding a generic public AppendFormatted method

Within this custom handler you can decide how to turn the interpolated string into a single string instance, for example by using a StringBuilder. In the code you could also enforce any custom formatting/length restrictions that are required.

The following code shows a simple example using a StringBuilder:

using System.Runtime.CompilerServices;
using System.Text;

namespace ConsoleApp1
{
    [InterpolatedStringHandler]
    public ref struct LogMessageInterpolatedStringHandler
    {
        readonly StringBuilder logMessageStringbuilder;
     
        public LogMessageInterpolatedStringHandler(int literalLength, int formattedCount)
        {
            logMessageStringbuilder = new StringBuilder(literalLength);
        }

        public void AppendLiteral(string s)
        {
            // For demo purposes
            Console.WriteLine($"AppendLiteral called for '{s}'");

            logMessageStringbuilder.Append(s);
        }

        public void AppendFormatted<T>(T t)
        {
            // For demo purposes
            Console.WriteLine($"AppendFormatted called for '{t}'");

            logMessageStringbuilder.Append(t?.ToString());
        }

        public string BuildMessage() => logMessageStringbuilder.ToString();
    }
}

To make the logging class use this we can add another overload of the log method that instead of a string takes a LogMessageInterpolatedStringHandler:

public static void Log(LogMessageInterpolatedStringHandler logMessageBuilder)
{
    if (IsLoggingEnabled)
    {
        Console.WriteLine("...interpolating message because logging is enabled...");
        Console.WriteLine(logMessageBuilder.BuildMessage());
    }
    else
    {
        Console.WriteLine("...NOT interpolating message because logging is disabled...");
    }
}

Now if Log is called with a non-interpolated string like "Hello - this is not an interpolated string" the original log method will be used.

If the Log method is called with an interpolated string, the custom handler will be invoked (if we choose to invoke it). For example, if logging is disabled we don’t even need to call the handler to build the final log message:

public static void Log(LogMessageInterpolatedStringHandler logMessageBuilder)
{
    if (IsLoggingEnabled)
    {
        Console.WriteLine("...interpolating message because logging is enabled...");
        Console.WriteLine(logMessageBuilder.BuildMessage());
    }
    else
    {
        Console.WriteLine("...NOT interpolating message because logging is disabled...");
    }
}

The final code looks like this:

namespace ConsoleApp1
{
    // Simplified implementation
    public static class SimpleConsoleLogger
    {
        public static bool IsLoggingEnabled { get; set; }

        public static void Log(string message)
        {
            Console.WriteLine("...may have already interpolated the message...");

            if (IsLoggingEnabled)
            {
                Console.WriteLine(message);
            }            
        }

        public static void Log(LogMessageInterpolatedStringHandler logMessageBuilder)
        {
            if (IsLoggingEnabled)
            {
                Console.WriteLine("...interpolating message because logging is enabled...");
                Console.WriteLine(logMessageBuilder.BuildMessage());
            }
            else
            {
                Console.WriteLine("...NOT interpolating message because logging is disabled...");
            }
        }

    }
}


using System.Runtime.CompilerServices;
using System.Text;

namespace ConsoleApp1
{
    [InterpolatedStringHandler]
    public ref struct LogMessageInterpolatedStringHandler
    {
        readonly StringBuilder logMessageStringbuilder;
     
        public LogMessageInterpolatedStringHandler(int literalLength, int formattedCount)
        {
            logMessageStringbuilder = new StringBuilder(literalLength);
        }

        public void AppendLiteral(string s)
        {
            // For demo purposes
            Console.WriteLine($"AppendLiteral called for '{s}'");

            logMessageStringbuilder.Append(s);
        }

        public void AppendFormatted<T>(T t)
        {
            // For demo purposes
            Console.WriteLine($"AppendFormatted called for '{t}'");

            logMessageStringbuilder.Append(t?.ToString());
        }

        public string BuildMessage() => logMessageStringbuilder.ToString();
    }
}


SimpleConsoleLogger.IsLoggingEnabled = true;
SimpleConsoleLogger.Log($"{DateTime.Now}: starting...");
SimpleConsoleLogger.Log("Hello - this is not an interpolated string");
SimpleConsoleLogger.IsLoggingEnabled = false;
SimpleConsoleLogger.Log($"{DateTime.Now}: ending...");

And if we run this:

AppendFormatted called for '30/11/2021 11:52:02 AM'
AppendLiteral called for ': starting...'
...interpolating message because logging is enabled...
30/11/2021 11:52:02 AM: starting...
...may have already interpolated the message...
Hello - this is not an interpolated string
AppendFormatted called for '30/11/2021 11:52:02 AM'
AppendLiteral called for ': ending...'
...NOT interpolating message because logging is disabled...

Now the performance overhead of interpolating the strings will only happen if logging is enabled.

There’s a much more in-depth tutorial in the docs.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

Viewing all 207 articles
Browse latest View live


Latest Images