Builder Pattern as Ref Struct
Hey all, I'm trying to make a source-generated builder which I want to be a struct. I'm running into a problem when trying to chain the entire build, but if I capture the builder in a variable first I can chain it as much as I like. Wondering if there's any language feature/etc that would help me out here? What I'd like is to use the same builder struct instance and not have to copy it with each chained method.
EDIT:
The problem occurs because the extension method operates on a
ref
of the instance, so I must have a field/variable declared beforehand of the type. This means I can't just call new().Extension()
, as the result of new()
must be assigned to something before it can be used. That is what I am trying to resolve.32 Replies
May I ask, why are you using a ref struct?
It’s a builder. Why would it ever need boxed?
Specifically it’s only for setting properties of an immutable type.
ref structs cannot be used in async methods @Stroniax
It's... not async?
Update: the
ref struct
part is unimportant really. It could be a regular struct. Its the ref this
part I'm having trouble with. OP edited to clarify.Shouldnt the return just be
return b;
since b is already a refno, it'll convert to a value return
interesting
Apparently chatgpt says that by avoiding using the constructor directly you can fix this issue, in which MyBuilder Create() => new();
"The issue you're encountering stems from the fact that when you chain methods directly on the result of a constructor, the compiler treats that instance as a temporary value, not as an assignable variable. To resolve this, you can introduce a method that creates and returns a builder instance, allowing you to chain methods on it without running into the assignment error."
@MKP unfortunately I've tried that as well, to no avail.
What's weird is that I don't need to assign intermediate stages.
That "to" in that sentence took me through a run
The factory is not working for me
Well yeah you have to take it out to a variable thats what a ref return does
Please ensure mwe's actually build
Like whats this My type
The point is that this won't build, I'm trying to avoid having to assign to a field/variable first while mutating and returning the same struct instance, that's my primary goal. I've added a
My
definition if necessary, but the point is that the builder just builds into it.Well if you want others to triage they should be able to run the code
If the code ran, I wouldn't be needing help here 🙂 Let me see if I can come up with a more simple example
@MKP New example that I believe is more concise. Does this help?
SharpLab
C#/VB/F# compiler playground.
I was able to do something horrible to fix this
Oh shoot. I'm OK with that if it's safe to do?
Wait, I didn't see the static
self
. That's not at all thread safe, is it?Its not thread safe no
And dangerous to use
Cuz if you continue any other builder then it explodes
👍 that is what I was afraid of, thanks for confirming
I think I will just document and instruct users to create the builder in a separate line from the build chain. Not as great UX but it's what works.
Yeah you cant return a local ref
I mean you could probably force something to happen with memory allocations
But at what cost
I rather you just create a class instead
Or what if I do
Unsafe.AsRef(in builder)
inside the create method?
I mean it's... unsafe, I'm sure there's a reason for that which I simply don't understand of why not to use that method here.SharpLab
C#/VB/F# compiler playground.
Because that ref will get garbage collected
You are essentially accessing memory unsafely from a zombie object
You cant explicitly force a GC cycle in C# sadge
But in theory it will explode randomly if that memory then gets used for something else
Gotcha. Good reason to avoid it then.
✅
Remember that this builder then will not be able to be used in any async method.
Because you simply cannot have a ref struct variable in async code.
Like
Span
. It's nbd to call it through a sync method.
I dont like your example code
@MKP does this help? Based on the example code in OP.
Records are just classes with configured a certain way
There are record structs, but thats the same way
Yes, I'm not sure what you disliked about my example code
Oh. I much prefer you using FooBar
Because "My" isnt a noun
Oh gotcha.
I will try to use more common nouns.