Wednesday, February 22, 2023

C# "var" with a Reference Type is Always Nullable

As an addition to the series on nullability in C#, we should look at the "var" keyword. This is because "var" behaves a little differently than it did before nullable reference types. Specifically, when nullability is enabled, using "var' with a reference type always results in a nullable reference type.

Articles

The source code for this article series can be found on GitHub: https://github.com/jeremybytes/nullability-in-csharp.

The Short Version

Using "var" with a reference type always results in a nullable reference type.

    var people = new List<Person> {...}
When we hover over the "people" variable name, the pop-up shows us the type is nullable:
    (local variable) List<Person>? people
So even though the "people" variable is immediately assigned to using a constructor, the compiler still marks this as nullable.

Let's take a look at how this is a little different than it was before.

Before Nullable Reference Types

Before nullable reference types, we could expect the type of "var" to be the same as the type of whatever is assigned to the variable.

Consider the following code:
    Person person1 = GetPersonById(1);
    var person2 = GetPersonById(2);
In this code, both "person1" and "person2" have the same type: "Person" (assuming that this is the type that comes back from "GetPersonById").

We can verify this by hovering over the variable in Visual Studio. First "person1":

    (local variable) Person person1
This shows us that the type of "person1" is "Person".

Now "person2":
    (local variable) Person person2
This shows us that the type of "person2" is also "Person".

The lines of code are equivalent. "var" just acts as a shortcut here. And for those of us who have been coding with C# for a while, this is what we were used to.

If you would like a closer look at "var", you can take a look at "Demystifying the 'var' Keyword in C#".

After Nullable Reference Types

But things are a bit different when nullable reference types are enabled.

Here is the same code:
    Person person1 = GetPersonById(1);
    var person2 = GetPersonById(2);
When we hover over the "person1" variable, Visual Studio tells us that this is a "Person" type (what we expected.
    (local variable) Person person1
When we hover over the "person2" variable, Visual Studio tells us that this is a "Person?" type -- meaning it is a nullable Person.
    (local variable) Person? person2
So this shows us that the type is different when we use "var". In the code above, "GetPersonById" returns a non-nullable Person. But as we saw in the first article in the series, that is not something that we can rely on at runtime.

The Same with Constructors

You might think that this behavior only applies when we assign a variable based on a method or function call, but the behavior is the same when we use a constructor during assignment.

In the following code, we create a variable called "people" and initialize it using the constructor for "List<Person>":

    var people = new List<Person> {...}
When we hover over the "people" variable name, the pop-up shows us the type is nullable:
    (local variable) List<Person>? people
So even though the "people" variable is assigned based on a constructor call, the compiler still marks this as nullable.

Confusing Tooling

One reason why this was surprising to me is that I generally use the Visual Studio tooling a bit differently then I have used it here. And the pop-ups show different things depending on what we are looking at.

Hover over "var"
Normally when I want to look at a type of a "var" variable, I hover over the "var" keyword. Here is the result:
class System.Collections.Generic.List<T>
...
T is Person

This tells us that the type of "var" is "List<T>" where "T is Person". This means "List<Person>". Notice that there is no mention of nullability here.

Hover over the variable name
However, if we hover over the name of the variable itself, we get the actual type:

    (local variable) List<Person>? people
As we've seen above, this shows that our variable is, in fact, nullable.

The Documentation

There's documentation on this behavior on the Microsoft Learn Site: Declaration Types: Implicitly Typed Variables. This gives some insight into the behavior:


"When var is used with nullable reference types enabled, it always implies a nullable reference type even if the expression type isn't nullable. The compiler's null state analysis protects against dereferencing a potential null value. If the variable is never assigned to an expression that maybe null, the compiler won't emit any warnings. If you assign the variable to an expression that might be null, you must test that it isn't null before dereferencing it to avoid any warnings."
This tells us that the behavior supports the compiler messages about potential null values. Because there is so much existing code that uses var, there was a lot of potential to overwhelm devs with "potential null" messages when nullability is enabled. To alleviate that, "var" was made nullable.

Should I Worry?

The next question is whether we need to worry about this behavior. The answer is: probably not. In most of our code, we will probably not notice the difference. Even though the "var" types are technically nullable, they will most likely not be null (since the variables get an initial assignment).

But it may be something to keep in the back of your mind when working with "var" and nullable reference types. If you are used to using "var" in a lot of different situations, you just need to be aware that those variables are now nullable. I know of at least 1 developer who did run into an issue with this, but I have not heard about widespread concerns.

Wrap Up

It's always "fun" when behavior of code changes from what we are used to. I did not know that this behavior existed for a long time -- not until after I saw some high-profile folks talking about it online about 2 months ago. I finally put this article together because I am working on a presentation about nullable reference types that I'll be giving in a couple months.

My use of "var" is not changing due to this. Historically, if the type was clear based on the assignment (such as a constructor), then I would tend to use "var". If the type was not clear based on the assignment (such as coming from a not-too-well-named method call), then I tend to be more explicit it my types.

Of course, a lot of this changed with "target-typed new" expressions (from C# 9). But I won't go into my thoughts on that right now.

Happy Coding!

Tuesday, January 17, 2023

Checking for Overflow in C#

By default, integral values (such as int, uint, and long) do not throw an exception when they overflow. Instead, they "wrap" -- and this is probably not a value that we want. But we can change this behavior so that an exception is thrown instead.

Short version:

Use a "checked" statement to throw an overflow exception when an integral overflow (or underflow) occurs.

As an alternate, there is also a project property that we can set: CheckForOverflowUnderflow.

Note: This behavior applies to "enum" and "char" which are also integral types in C#.

Let's take a closer look at this.

Default Behavior

Let's start by looking at the default overflow behavior for integer (Int32) -- this applies to other integral types as well.

    int number = int.MaxValue;
    Console.WriteLine($"number = {number}");

    number++;
    Console.WriteLine($"number = {number}");

This creates a new integer variable ("number") and sets its value to the maximum value that a 32-bit integer can hold.

When we increment that value (using the ++ operator), it goes past that maximum value. But instead of throwing an error, the value "wraps" to the lowest integer value.

Here is the output of the above code:

    number = 2147483647
    number = -2147483648

This is probably not the behavior that we want.

Using a "checked" Statement

One way we can fix this is to use a "checked" statement. This consists of the "checked" operator and a code block.

    int number = int.MaxValue;
    Console.WriteLine($"number = {number}");
    checked
    {
        number++;
    }
    Console.WriteLine($"number = {number}");
  

The result of this is that the increment operation is checked to see if it will overflow. If it does, it throws an OverflowException.

Here's the exception if we run this code from Visual Studio:

    System.OverflowException: 'Arithmetic operation resulted in an overflow.'

Let's handle the exception:

    try
    {
        int number = int.MaxValue;
        Console.WriteLine($"number = {number}");

        checked
        {
            number++;
        }
        Console.WriteLine($"number = {number}");
    }
    catch (OverflowException ex)
    {
        Console.WriteLine($"OVERFLOW: {ex.Message}");
    }

Now our output looks like this:

    number = 2147483647
    OVERFLOW: Arithmetic operation resulted in an overflow.

If we overflow an integer value, it probably means that we need to make some changes to the code (such as using a larger type). But the good news is that an overflow exception makes sure that we do not unintentionally use an invalid value.

Using "CheckForOverflowUnderflow"

We may want to apply overflow checks through our entire project. Instead of using individual "checked" statements, we can also set a property on the project: "CheckForOverflowUnderflow".

To see this in action, we will removed the "checked" statement from our code:

    try
    {
        int number = int.MaxValue;
        Console.WriteLine($"number = {number}");

        number++;
        Console.WriteLine($"number = {number}");
    }
    catch (OverflowException ex)
    {
        Console.WriteLine($"OVERFLOW: {ex.Message}");
    }

The code does not throw an exception, and we are back to the wrapped value:

    number = 2147483647
    number = -2147483648

In the project, we can add the "CheckForOverflowUnderflow" property. Here is the project file for our basic console application:

    <Project Sdk="Microsoft.NET.Sdk">

      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net7.0</TargetFramework>
        <RootNamespace>check_overflow</RootNamespace>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
      </PropertyGroup>

    </Project>

We've set the "CheckForOverflowUnderflow" property to "true". Now when the application runs, the exception is thrown.

    number = 2147483647
    OVERFLOW: Arithmetic operation resulted in an overflow.

"unchecked"

In addition to "checked", there is also an "unchecked" operator. As you might imagine, this does not check for overflow.

So with our project set to "CheckForOverflowUnderflow", we can add an "unchecked" block that will ignore the project setting.

    try
    {
        int number = int.MaxValue;
        Console.WriteLine($"number = {number}");

        unchecked
        {
            number++;
        }
        Console.WriteLine($"number = {number}");
    }
    catch (OverflowException ex)
    {
        Console.WriteLine($"OVERFLOW: {ex.Message}");
    }

The code does not throw an exception, and we are back to the wrapped value:

    number = 2147483647
    number = -2147483648

Wrap Up

Normally, I do not need to worry about overflow or underflow in my code; it's not something that comes up in my applications very often.

One exception is with Fibonacci sequences. I've written a few articles involving Fibonacci sequences (including "Implementing a Fibonacci Sequence with Value Tuples in C# 7" and "Coding Practice: Learning Rust with Fibonacci Numbers"). Since the sequence more or less doubles on each item, it overflows a 32-bit integer very quickly (around the 46th item in the sequence). This is one place where I generally use a larger type (like a long) and also a "checked" statement to make sure I do not end up using invalid values in my code somewhere.

"checked" statements do come with a cost. There is a little overhead that is added in the arithmetic operations. Because of this, I generally leave projects with the default setting ("unchecked" for the project), and then use targeted "checked" statements where I need them.

It's always interesting to find things like this in the C# language. I don't often need it, but it's really good to have it when I do.

Happy Coding!

Monday, January 16, 2023

Full-Day Workshop - Asynchronous & Parallel Programming in C# (March 2023)

I'll be giving a full day workshop (with hands-on labs) at Visual Studio LIVE!, Las Vegas on March 19th, 2023. Now's your chance to spend a full day with me and also learn tons of useful tech during the rest of the week.

Event Info:

Visual Studio LIVE! March 19-24, 2023
Planet Hollywood Resort & Casino
Las Vegas NV
Event Link: https://vslive.com/events/las-vegas-2023/home.aspx

Use the promo code "Clark" to save $500 off the regular price for 4-, 5-, or 6-day packages (Note: you'll need the 6-day package to join me for the full day workshop on Sunday, March 19). Here's a direct link to registration that includes the promo code: http://bit.ly/3HSDHJn

Read on to see what we'll learn in the workshop.

Hands-on Lab: Asynchronous and Parallel Programming in C#

3/19/2023 9:00 a.m. - 6:00 p.m.
Level: Intermediate

Asynchronous programming is a critical skill to take full advantage of today's multi-core systems. But async programming brings its own set of issues. In this workshop, we'll work through some of those issues and get comfortable using parts of the .NET Task Parallel Library (TPL).

We'll start by calling asynchronous methods using the Task Asynchronous Pattern (TAP), including how to handle exceptions and cancellation. With this in hand, we'll look at creating our own asynchronous methods and methods that use asynchronous libraries. Along the way, we'll see how to avoid deadlocks, how to isolate our code for easier async, and why it's important to stay away from "asyc void".

In addition, we'll look at some patterns for running code in parallel, including using Parallel.ForEachAsync, channels, and other techniques. We'll see pros and cons so that we can use the right pattern for a particular problem.

Throughout the day, we'll go hands-on with lab exercises to put these skills into practice.

Objectives:

  • Use asynchronous methods with Task and await 
  • Create asynchronous methods and libraries 
  • How to avoid deadlocks and other pitfalls 
  • Understand different parallel programming techniques

Topics:

Here's a list of some of the topics that we'll cover:

Pre-Requisites:

Basic understanding of C# and object-oriented programming (classes, inheritance, methods, and properties). No prior experience with asynchronous programming is necessary; we'll take care of that as we go.

Attendee Requirements:

  • You must provide your own laptop computer (Windows, Mac, or Linux) for this hands-on lab.
  • All other laptop requirements will be provided to attendees 2 weeks prior to the conference

Hope to See You There!

If you can't make it to this one, keep watching here (and my website: jeremybytes.com) for future public workshops. I'm also available for private workshops for your team - customized to be most relevant to the code that you're building.

Happy Coding!