Careful with that Struct
Posted on January 10, 2026 in dotnet
Method Chaining is a general pattern where an object class implements methods that alter the state of the object, and return the current instance, thus allowing code such as:
var c = new C();
c.ChangeThis(123).ChangeThat(456);
For instance, this very minimal C# example:
struct C
{
private string? _s;
public C Change(string s)
{
_s = s;
return this;
}
public override string ToString() => _s;
}
Could be used with the following results:
var c = new C();
c.Change("a");
Console.WriteLine(c); // writes 'a'
c.Change("b").Change("c");
Console.WriteLine(c); // writes 'c'
Now let's say we want to be clever and for some reason we decide to implement the pattern on a mutable struct:
struct C
{
private string? _s;
public C Change(string s)
{
_s = s;
return this;
}
public override string ToString() => _s;
}
Can you predict the output of this code?
var c = new C();
c.Change("a");
Console.WriteLine(c); // writes ??
c.Change("b").Change("c");
Console.WriteLine(c); // writes ??
Turns out, the first line will be 'a', but the second line will be 'c'. How come?
It goes like this. We know that struct are passed around by value. More precisely, they are copied around when passed or returned to functions. Except of course for their own functions, which act on the struct itself. So when the Change method is invoked directly on the c variable, it operates on the actual structure owned by the c variable, and thus mutate it. This is why the first line writes 'a'.
However, the Change method also need to return a C instance. And we just reminded ourselves that struct instances are copied around, so Change returns a copy of the structure owned by the c variable. At that point, both the structure in the c variable, and its copy, have a 'b' value.
But the chained method then operates on the copy! The copy is modified, and the result is ignored. The original structure owned by the c variable is not modified a second time. The second line writes 'b'.
Of course, if we do not ignore the returned value, things work as "expected":
c = c.Change("b").Change("c");
Console.WriteLine(c); // writes 'c'
So what is the conclusion here? Of course, it could be "do not mutate structs", which certainly is a valid general principle, but maybe a bit radical. On the other hand, it seems fairly reasonable to recommend against chaining methods for mutable structs.
There used to be Disqus-powered comments here. They got very little engagement, and I am not a big fan of Disqus. So, comments are gone. If you want to discuss this article, your best bet is to ping me on Mastodon.