The C# compiler, under some circumstances involving readonly
, creates defensive copies of a struct. While this issue is well known and documented, it’s worth revisiting as it’s tied to several features of C# 7.2. The in
and ref readonly
keywords make occurrences of the issue more frequent, while readonly structs offer a way to fix it.
Structs in C# are generally used for high-performance purposes to avoid the cost of memory allocation/dealocation. However, the potential pitfalls have restrained their use. C# 7.2 adds an improvement, readonly structs, to deal with the most common occurrences of the issue.
The C# compiler will create copies of a struct if:
- The struct signature is not readonly.
- The struct variable has the readonly modifier.
- A method (including properties) is called.
public struct SomeStruct
{
private int _x;
public int X { get { return _x; } }
}
private readonly SomeStruct s = new SomeStruct(42);
s.X; // Compiler creates a defensive copy.
The same rules apply when x
is an in
-parameter, a ref readonly
local variable or a result of a method invocation that returns a value by readonly reference.
public void BadFunction(in SomeStruct s)
{
s.X; // Compiler creates a defensive copy.
}
C# 7.2 adds the possibility to declare a struct readonly, providing a solution to avoid defensive copies. Structs declared as readonly can’t have property setters and prevents assignation of the struct members.
The defensive copies issue can be detected through static analysis. ErrorProne.NET is inspired from ErrorProne, a static analysis tool for Java. The .NET port is a set of Roslyn analyzers with a focus on correctness and performance. A subset of analyzers focuses on structs and is available in a Nuget package.