# What is New here? - aka 4 ways or creating structs

In this post I will investigate 4 different approaches for creating structs and I will measure their performance characteristics.

The release of C# 10 removed a previous restriction on struct constructors: structs are allowed have default constructors. This means the code below compiles without a build error.

```csharp
public struct Person
{
    public int Age { get; set; } = 1;

    public string Name { get; set; } = "Foo";
}
```

In this case the default constructor will set the property values as `1` and `Foo` respectively. This behavior is really similar to classes. The 4 ways this post explores creating structs:

1. Using the `new` keyword
1. Using the `default` keyword
1. Using generics and `new()` constraint
1. Using generics and `struct` constraint

The rest of the post shows the code sample for each option, compares performance from mean execution time and memory point of view, and finally looks into the IL/JITted assembly code to reason about the performance findings. For  microbenchmarks the [BenchmarkDotNet](https://benchmarkdotnet.org/) library is in .NET6 and Windows 11 platform.

## Using the `new` keyword

```csharp
public Person New()
{
  return new Person();
}
```

This code returns a *new* `Person` with the default constructor invoked, so the struct returned has the Name property set to `"Foo"` and the Age property to `1`.

## Using the `default` keyword

```csharp
public Person Default()
{
  return default(Person);
}
```

This method uses the `default` keyword to create the `Person` struct. The default constructor will not execute and all the fields are set to 0 or `null`, whether it is a value or reference type. In case of the `Person` struct, the *Age* is set to 0, and the *Name* is `null`.

## Using generics and `new()` constraint

A generic solution is often needed in case serialization and deserialization. A library performing the serialization might not presume the type of the source or target object, hence needs to use a *generic* approach.

```csharp
public Person GenericNew()
{
  return CreateNew<Person>();
}

private T CreateNew<T>() where T : new()  => new();
```

In this case the code is structured into two methods: a generic `CreateNew<T>` method has a `new()` [constraint](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/new-constraint) which means that the type must have a public parameterless constructor: `new T()`.

For creating the generic object, C# compiler emits a call to [Activator.CreateInstance](https://docs.microsoft.com/en-us/dotnet/api/system.activator.createinstance?view=net-6.0), which creates a new instance of the type.

```IL
IL_0000: call !!0 [System.Runtime]System.Activator::CreateInstance<!!T>()
```

## Using generics and `struct` constraint

```csharp
public Person GenericDefault()
{
  return CreateDefault<Person>();
}

private T CreateDefault<T>() where T : struct => default;
```

The code is still structured into two methods, but now the generic `CreateDefault<T>()` uses a `struct` [constraint](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#delegate-constraints) to enforce, that only structs may be used as generic type parameters. It allows to use the `default` keyword to get the struct created, while not allowing nulls for reference types. This the C# compiler uses the same [initobj](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.initobj?view=net-6.0) IL instruction as the non-generic method to create the struct.

The next section of this post compares these methods from performance point of view.

## Benchmarks

These benchmarks are captured using BenchmarkDotNet on .NET6, Windows 11 Build 21H2 on x64 platform with x64 RyuJIT.
```
|         Method |       Mean |     Error |    StdDev |     Median |  Gen 0 | Allocated |
|--------------- |-----------:|----------:|----------:|-----------:|-------:|----------:|
|        Default |  0.0002 ns | 0.0007 ns | 0.0011 ns |  0.0000 ns |      - |         - |
|            New |  1.6940 ns | 0.0689 ns | 0.1225 ns |  1.7064 ns |      - |         - |
|     GenericNew | 14.8469 ns | 0.3227 ns | 0.7544 ns | 14.6591 ns | 0.0102 |      32 B |
| GenericDefault |  0.0393 ns | 0.0278 ns | 0.0803 ns |  0.0000 ns |      - |         - |
```

The *Default* and *GenericDefault* cases are close to be equivalent of an empty method call, mostly because they don't need to do anything, just return memory that is zero-ed out. The *New* method takes somewhat longer, but note, that the default constructor of the struct executes this time, setting the *Age* and *Name* properties. So far these cases don't cause any memory allocation on the heap, which is desired in case of using structs. However, *GenericNew* performance lies out as it both allocates memory on the heap and executes significantly slower to the other methods.

Let's observer the JITted assembly code to reveal the inner workings.

## Jitted Code

### *Default* and *GenericDefault*

As previously pointed out, *Default* and *GenericDefault* use the same IL instructions. After optimizations and inlining, the runtime emits identical assembly code:

```assembly
       xor       eax,eax
       mov       [rdx],rax
       mov       [rdx+8],eax
       mov       rax,rdx
       ret
; Total bytes of code 12
```

In the first instruction 0 is set in the `eax` register. Then the two fields (Name and Age) are overwritten with this value.

### *New* assembly code

```assembly
; Benchmarks.New()
       push      rsi
       mov       rsi,rdx
       mov       rdx,276D3CDA038
       mov       rdx,[rdx]
       mov       rcx,rsi
       call      CORINFO_HELP_CHECKED_ASSIGN_REF
       mov       dword ptr [rsi+8],1
       mov       rax,rsi
       pop       rsi
       ret
; Total bytes of code 37
```

The size of the code is more than 3 times larger in bytes compared to *Default* and *GenericDefault* methods. This is because it needs to execute the code for the default constructor. For the *Name* property the `"Foo"` string has to be loaded and set (which involves also executing a write barrier), while for the *Age* property the value 1 is written to the `rsi+8` address.

### *GenericNew* assembly code

Finally, the largest code is required for the *GenericNew* method.

```assembly
; Benchmarks.GenericNew()
       mov       rcx,rdx
       jmp       near ptr System.Activator.CreateInstance[[Person, StructConstructor]]()
; Total bytes of code 8
```
```assembly
; System.Activator.CreateInstance[[Person, StructConstructor]]()
       push      rdi
       push      rsi
       push      rbx
       sub       rsp,20
       mov       rbx,rcx
       mov       rcx,offset MT_Person
       call      CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE
       mov       rcx,rax
       call      System.RuntimeType.CreateInstanceOfT()
       mov       rsi,rax
       mov       rdx,offset MT_Person
       cmp       [rsi],rdx
       je        short M01_L00
       mov       rdx,rsi
       mov       rcx,offset MT_Person
       call      CORINFO_HELP_UNBOX
M01_L00:
       add       rsi,8
       mov       rdi,rbx
       call      CORINFO_HELP_ASSIGN_BYREF
       movsq
       mov       rax,rbx
       add       rsp,20
       pop       rbx
       pop       rsi
       pop       rdi
       ret
; Total bytes of code 94
```

Most of the code is required for implementing `Activator.CreateInstance` method. It needs to load the type handle for `Person`. It needs to create the given instance for the type: the call to `System.RuntimeType.CreateInstanceOfT()` will instantiate a new object and call its default constructor. It also needs to unbox the returned object, which involves further runtime type checking. All these extra operations result in a performance cost compared to the other methods. Because the object gets created on the heap, it accounts for the generation 0 allocations in the benchmark results. Unboxing involves further copies, the data, making this method ultimately the slowest performing solution.

## Conclusion

C# 10 makes struct creation mentally more involved, as developers have to keep track the different ways and outcomes of struct creation. A rather unfortunate outcome is the performance trap of using generics with `new()` constraint and structs, which results in a performance penalty compared to the other options presented in this post.


