While experimenting with C# 9 record, I came across a rather strange behavior. My record looks like this:
record MyRecord(Func<MyRecord> SomeAction, string Name)
{
public MyRecord(string name) : this(null, name)
{
SomeAction = Foo;
}
// Foo returns 'this' with SomeAction changed to Bar
MyRecord Foo()
{
Console.WriteLine("Foo: " + SomeAction.Method.Name);
return this with { SomeAction = Bar };
}
MyRecord Bar()
{
Console.WriteLine("Bar: " + SomeAction.Method.Name);
return this;
}
}
And I use it like this:
class Program
{
static void Main(string[] args)
{
var r = new MyRecord("Foo");
Console.WriteLine(r.ToString());
r = r.SomeAction();
r = r.SomeAction();
r = r.SomeAction();
}
}
The output I expected would be
Foo: Foo
Bar: Bar
Bar: Bar
However, the actual output I got:
Foo: Foo
Bar: Foo
Foo: Foo
Is this a bug or am I missing something?
return this with { SomeAction = Bar };
Captures Bar
on the original record, not the updated record. And on the original record, SomeAction.Method.Name
is Foo
. Since Bar
returns this
, and Bar
is the Bar
of the original record, the second line returns the original record, which explains why the third line is the same as the first.
It will be easier to understnd if we rewrite it like this:
class Program
{
static void Main(string[] args)
{
var r = new MyRecord("Foo");
Console.WriteLine(r.ToString());
var r1 = r.SomeAction(); // calls r.Foo, returns new instance of record, capturing r.Bar
var r2 = r1.SomeAction(); // calls r.Bar, and returns r.
var r3 = r2.SomeAction(); // calls r.Foo, returns new instance of record, capturing r.Bar
}
}
To get your expected behavior, you would have to do something like this:
record MyRecord(Func<MyRecord> SomeAction, string Name)
{
public MyRecord(string name) : this(null, name)
{
SomeAction = Foo;
}
// Foo returns 'this' with SomeAction changed to Bar
MyRecord Foo()
{
Console.WriteLine("Foo: " + SomeAction.Method.Name);
MyRecord updated = null;
updated = this with { SomeAction = () => updated.Bar() };
return updated;
}
MyRecord Bar()
{
Console.WriteLine("Bar: " + SomeAction.Method.Name);
return this;
}
}
However I wouldn't recommend this approach - it is very difficult to follow.