How does the compiler infer the type of default?

 
 
  • Gérald Barré

The default keyword is a powerful tool in C#. For reference types, it returns null, and for value types (structs), it returns a zeroed-out value. Interestingly, default(T) and new T() can yield different results for structs (check out Get the default value of a type at runtime for more info). Initially, C# required default(T), but now you can simply use default when the type can be inferred by the compiler.

But how does the compiler infer the type? And can you always trust it to get it right? Let's dive in and find out!

Let consider two simple cases:

C#
// Infer from the left hand side
int foo = default;

// Infer from the parameter type
Foo(default);
void Foo(int bar) => throw null;

The previous cases are straightforward. The compiler can infer the type from the left-hand side or the parameter type. But what happens with a switch expression?

C#
var sample = new byte[0];
ReadOnlyMemory<byte> foo = sample switch
{
    byte[] value => value,
    _ => default,
};

You may expect default to be default(ReadOnlyMemory<byte>) as this is the visible type on the left-hand side. However, it is actually default(byte[]). Indeed, the compiler infers the type from the switch expression arms. In this case, the compiler will determine the best common type for all the cases, which in this case is byte[]. Therefore, the type of default is byte[].

In the previous example, the result would be the same if you use default(byte[]) or default(ReadOnlyMemory<byte>). But, this can have some implications. Let's switch the type from ReadOnlyMemory<byte> to ReadOnlyMemory<byte>? (nullable).

C#
var sample = new object();
ReadOnlyMemory<byte>? foo = sample switch
{
    byte[] value => value,
    _ => default, // default(byte[])
};

In the previous example, the type of default is still default(byte[]). So, the switch expression will return default(byte[]) which is null. But, the value is converted to ReadOnlyMemory<byte>? which is not null, but an empty ReadOnlyMemory<byte>. Therefore, foo.HasValue will be true!

In your IDE, you can visualize the actual type of default by hovering over it. For example, in Visual Studio, you can see the actual type in the tooltip:

Visual Studio showing the actual type of defaultVisual Studio showing the actual type of default

If you think VS should be more explicit, you can vote for this issue

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub