Defensive Copy In .NET C#
top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

  • Writer's pictureAhmed Tarek

Defensive Copy In .NET C#

Updated: Apr 17

Understand why Defensive Copy is important and how it affects performance.


Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Defensive Copy In .NET C#. Image by Ahmed Tarek

Did you ever hear about Defensive Copy in .NET C#?


Do you know how important this topic is?


Do you know that you might be wasting memory and processing power because of it?


Let me tell you about it…


 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Brain Teaser. Image by Ahmed Tarek

Brain Teaser


Before jumping into any details, let me show you something interesting.


Let’s say that we have this struct defined as follows:


public struct Num
{
    public int Value;

    public Num(int value)
    {
        Value = value;
    }

    public void Increment()
    {
        Value++;
    }

    public override string ToString() => $"Value = {Value.ToString()}";
}

As you can see, it is a simple Num struct with:

  • An int field called Value .

  • A constructor.

  • A method called Increment which increases Value field by 1.

  • A ToString method override.


Now, let’s say that we have the following code:


public class MainProgram
{
    private Num _number = new Num(1);

    public void Run()
    {
        Console.WriteLine("Before Increment: " + _number.ToString());
        _number.Increment();
        Console.WriteLine("After Increment: " + _number.ToString());
    }
}

In this class, we just defined a private field of type Num, and in the Run method we just increment the field by calling its own Increment method.


If we run this code, we would get this result:


Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Expected result. Image by Ahmed Tarek

As expected, right?


Now, let’s apply a minor change on the code and see how this would reflect.


Let’s modify the code to be as follows:


public class MainProgram
{
    private readonly Num _number = new Num(1);

    public void Run()
    {
        Console.WriteLine("Before Increment: " + _number.ToString());
        _number.Increment();
        Console.WriteLine("After Increment: " + _number.ToString());
    }
}

As you can notice here, the only change we applied is adding the readonly keyword in the field declaration.


Now, if we run the code again, do you expect a different result than the previous one? Not sure? Let’s give it a try.


When we run the new code, we would get this result:


Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Unexpected result. Image by Ahmed Tarek

I can hear you now screaming:

What the hell? How is this possible?!!!

Yes, it is possible and it would actually happen every time as it is not a glitch in the matrix or something 😁


Let me explain it to you.


 


 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
A Glimpse Under The Hood. Photo by Evan Brorby on Unsplash, adjusted by Ahmed Tarek

A Glimpse Under The Hood


What actually happened is what I have been asking you about in the first lines of this article; it is Defensive Copy.


What happened could be broken down into simple steps:

  1. When we marked the field with the readonly keyword, this revealed our intention to keep this field totally untouched. In other words, we don’t want any changes to be applied to the object behind this field.

  2. Therefore, the compiler actually listened to us and understood our intentions. Thus, the compiler decided to help us achieve our goal.

  3. Then, we tried to increment the field by calling its own Increment method.

  4. Thus, this is where the compiler decided to kick in and protect our field object from any changes even if these changes are triggered from inside itself. But how would the compiler do it?

  5. The compiler would do it by first creating a copy of the field object and then applying the Increment call on it, not on the original field object.

  6. Worth mentioning here is that the field object is of the type Num which is a struct. As we know, copying a struct would yield a totally new object.

  7. Therefore, this would eventually protect our field object from any changes.


So, in simple words, this code:


public class MainProgram
{
    private readonly Num _number = new Num(1);

    public void Run()
    {
        Console.WriteLine("Before Increment: " + _number.ToString());
        _number.Increment();
        Console.WriteLine("After Increment: " + _number.ToString());
    }
}

Would eventually be translated into this code:


public class MainProgram
{
    private readonly Num _number = new Num(1);

    public void Run()
    {
        var number = _number;
        Console.WriteLine("Before Increment: " + number.ToString());

        number = _number;
        number.Increment();

        number = _number;
        Console.WriteLine("After Increment: " + number.ToString());
    }
}

Now you might ask:

But why would the compiler create a copy of the field object before the ToString call?!! This call is not expected to change the object by any means.

Yes, you are right. It is not expected to apply any changes on the field object but this is not the way the compiler thinks.


The compiler doesn’t inspect the code inside the method and decides if it is going to apply any changes to the object or not.


It just assumes that this could happen and this is good enough for the compiler to be cautious and apply the Defensive Copy mechanism.


I hope you now understand what actually happened. In the next section, we will talk more about the Defensive Copy mechanism.


 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Defensive Copy Mechanism. Photo by Josh Redd on Unsplash, adjusted by Ahmed Tarek

Defensive Copy Mechanism


Now, let’s go through the questions most probably you have in mind right now.


❓ When does it happen?

It happens when a struct object is used in a read-only context, and this object is manipulated.


❓ What do you mean by manipulated?

Manipulated means calling any method on the object. Also, calling a property is the same because the property is a method at the end. However, calling a field would not trigger the mechanism.


❓ What do you mean by read-only context?

It means when the object is declared as one of the following:



👉 readonly field


public class MainProgram
{
    private readonly Num _number = new Num(1);

    public void Run()
    {
        Console.WriteLine("Before Increment: " + _number.ToString());
        _number.Increment();
        Console.WriteLine("After Increment: " + _number.ToString());
    }
}


👉 ref readonly local variable


public class MainProgram
{
    private Num _number = new Num(1);

    public void Run()
    {
        ref readonly Num number = ref _number;

        Console.WriteLine("Before Increment: " + number.ToString());
        number.Increment();
        Console.WriteLine("After Increment: " + number.ToString());
    }
}


👉 in parameter


public class MainProgram
{
    public void Run(in Num number)
    {
        Console.WriteLine("Before Increment: " + number.ToString());
        number.Increment();
        Console.WriteLine("After Increment: " + number.ToString());
    }
}

❓ Does it really matter? Should we be concerned about whether Defensive Copy is triggered or not?


It actually depends on the size of the struct and how frequently the Defensive Copy mechanism is triggered.


👉 The bigger the struct is, the more impact we should expect.


👉 The more frequently the Defensive Copy mechanism is triggered, the more impact we should expect.


Let me show you…


 


 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Performance Impact. Photo by Kolleen Gladden on Unsplash, adjusted by Ahmed Tarek

Performance Impact


Let’s simplify the Num struct and add more fields to it just to make its size bigger. Then, the code should be as follows:


public struct Num
{
    // Fields to just make the struct bigger
    private long Field1, Field2, Field3, Field4;

    public long Value { get; }

    public Num(long value) : this()
    {
        Value = value;
    }
}

Now, let’s build a benchmarker project to compare the performance of:

👉 Field of the Num struct.

👉 readonly field of the Num struct.


[MemoryDiagnoser]
[RankColumn]
public class Benchmarker
{
    private const int Count = 1_000_000;
    private Num _number = new Num(1);
    private readonly Num _readonlyNumber = new Num(1);

    [Benchmark(Baseline = true)]
    public long UsingField()
    {
        long total = 0;

        for (var i = 0; i < Count; i++)
        {
            total += _number.Value;
        }

        return total;
    }

    [Benchmark]
    public long UsingReadonlyField()
    {
        long total = 0;

        for (var i = 0; i < Count; i++)
        {
            total += _readonlyNumber.Value;
        }

        return total;
    }
}

Running this benchmarker project, we would get the following result:


Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Benchmark result. Image by Ahmed Tarek

As we can notice from the result, using the field is 4 times faster than using the readonly field.


This could trigger you to ask the following question:

Ok, now I know that it could make a difference, but, is there a way to avoid this Defensive Copy when it is actually not needed??

Yes, I understand your point. Sometimes the compiler just triggers the Defensive Copy mechanism even when the calls are not going to apply any changes to the object.


It would be bad if we should always pay the bill just because the compiler wants to be cautious. But, should we??


 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
The Solution. Photo by Edge2Edge Media on Unsplash, adjusted by Ahmed Tarek

The Solution


No, we shouldn’t. We are not always enforced to accept pay this bill allocating more memory and wasting processing power. There is a solution.


The fix is as simple as marking the struct as readonly.


When we mark the struct as readonly, the compiler makes sure it is immutable. This means that no one can manipulate the object or change its state.


Accordingly, the compiler gets confident that no calls would manipulate the object, and therefore no Defensive Copy mechanism is needed.


Therefore, if we change the code to be as follows:


public readonly struct Num
{
    public readonly int Value;

    public Num(int value)
    {
        Value = value;
    }

    public Num Increment()
    {
        return new Num(Value + 1);
    }

    public override string ToString() => $"Value = {Value.ToString()}";
}

Then now if we run the same exact benchmarker project, we would get the following result:


Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Benchmark result. Image by Ahmed Tarek

See, marking the struct as readonly removed the Defensive Copy mechanism overhead saving memory and processing power.


 


 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Means of Detection. Photo by Flex Point Security Inc. on Unsplash, adjusted by Ahmed Tarek

Means of Detection


Now, you might be asking:

Is there any tool to help me spot Defensive Copy occurrence?

Yes, ErrorProne.NET Structs nuget package.


It is a group of analyzers that help avoid struct and readonly reference performance pitfalls. Using this nuget package you can spot struct-related problems so that you can fix them on the spot.


What you need to keep in mind is that the analyzers emit a diagnostic only when the struct size is >= 16 bytes.


It was said that you can change this threshold using .editorconfig file and adding the following line:


error_prone.large_struct_threshold = {new threshold}

 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
.NET Core. Image by Ahmed Tarek

.NET Core


All that we discussed up to this point applies to the .NET Framework. With .NET Core, things have changed.


The same concept of Defensive Copy still exists but the framework is now smarter. Some code that is falsely detected by the .NET Framework as manipulative would not be detected by the .NET Core.


 

Defensive Copy In .NET C# Struct Memory Allocation Compiler Optimization Enhance Performance Right Good Bad Impact Processing DotNet CSharp Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Final Thoughts. Photo by Kenny Eliason on Unsplash, adjusted by Ahmed Tarek

Final Thoughts


I hope by this point you already understood everything about the Defensive Copy mechanism.


My final advice to you is:

👉 Always try to design and implement structs to be immutable.

👉 This would make it so easy to mark structs as readonly.

👉 With structs always try to use fields, not properties. This would help you avoid too many problems.

👉 Even with .NET Core, you should follow the best practices and not only depend on the framework to take care of it.


That’s it. Hope you find reading this article as interesting as I found writing it.



Recent Posts

See All

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page
Mastodon Mastodon