Repository navigation
Properties as protocol members, and an IBufferWriter write overload - #18
Merged
Merged
Conversation
…load
A protocol member may now be a property as well as a field. The same attributes
work on both and the produced bytes are identical, which the first test asserts
directly: the same protocol declared once with fields and once with properties,
covering every supported type, has to come out byte for byte the same.
Properties cost nothing. The accessors were already compiled expressions,
MakeMemberAccess emits a call to the getter or setter, and the JIT inlines a
trivial auto property accessor into the same code a field access produces:
Read 38 byte POCO fields 58.91 ns properties 60.76 ns
Read into reused instance fields 31.24 ns properties 28.40 ns
Write 38 byte POCO fields 57.02 ns properties 56.83 ns
with identical allocation in every row. The old restriction came from FieldInfo
being wired through ConverterFieldInfo, FastInvoke and the range violation
event, not from anything about the conversion itself.
An init only or private setter is fine, so a POCO can stay immutable from the
outside and still be filled from a frame. A property that carries the attribute
but has no setter is rejected at Prepare() with a message that says why, since
reading a frame would have to assign it. Computed properties, indexers and
static properties without the attribute are ignored rather than rejected.
ConvertToByteArray(T, IBufferWriter<byte>) writes a frame into a pipeline's own
buffer. GetSpan is free to hand out more than was asked for, so the cursor gets
exactly the frame and Advance is told the frame length, not the span length,
which TestBufferWriterApi checks with a writer that deliberately over returns.
It costs 57.17 ns against 56.15 ns for a buffer you own yourself, and allocates
nothing either.
BREAKING: OnRangeViolationDelegate takes a MemberInfo instead of a FieldInfo,
because FieldInfo cannot describe a property. A handler that only reads the name
needs no change beyond the parameter type. ConverterFieldInfo<T>.FieldInfo is
now Member, typed MemberInfo, with MemberType alongside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Property support
A protocol member may now be a property as well as a field. The same attributes work on both.
The test that carries the weight is the first one: the same protocol declared once with fields and once with properties, covering every supported type (
sbytethroughdecimal,Guid,DateTime, enum, string), has to produce byte for byte the same frame. It does.Properties cost nothing
The accessors were already compiled expressions;
MakeMemberAccessemits a call to the getter or setter, and the JIT inlines a trivial auto property accessor into the same code a field access produces.The read row is 3% apart, the other two are inside the noise in the other direction. Allocation is identical everywhere.
The old restriction came from
FieldInfobeing wired throughConverterFieldInfo,FastInvokeand the range violation event, not from anything about the conversion itself.What is accepted and what is not
initonly or private setter is fine, so a POCO can stay immutable from the outside and still be filled from a frame. Verified empirically rather than assumed:Expression.Assignon aninitproperty compiles and runs.Prepare()with a message that says why, since reading a frame would have to assign it.boolproperty occupies one byte, not the fourMarshal.SizeOf(typeof(bool))reports. There is a test for exactly that, becauseGetByteCountand the writer would otherwise agree on the wrong number and nothing else would notice.IBufferWriter
ConvertToByteArray(T, IBufferWriter<byte>)writes a frame into a pipeline's own buffer.The trap it has to avoid:
GetSpan(n)may return more than n bytes. The cursor gets exactly the frame andAdvanceis told the frame length, not the span length.TestBufferWriterApichecks this with a writer that deliberately over-returns and fills the spare room with a marker, then writes several frames and asserts they stay contiguous.About a nanosecond for the
GetSpan/Advancepair, and nothing allocated.BREAKING
OnRangeViolationDelegatetakes aMemberInfoinstead of aFieldInfo, becauseFieldInfocannot describe a property. One word for most handlers:A handler that read
field.FieldTypehas to ask the member for it, which is the part that will not be obvious from the compiler error, so the README upgrade section spells it out.ConverterFieldInfo<T>.FieldInfois nowMember(aMemberInfo), withMemberTypealongside. It was removed rather than left returning null for properties, so the failure is a compile error and not aNullReferenceExceptionat runtime.README
Rewritten to lead with the numbers, per your request: the speed table is now the first thing after the one line description, with the zero allocation loop as a code sample, the "byte order is free" point, and the garbage-per-second figure at 100k msg/s. The
baseline-before-v4.mdpointer sits next to it, since the credibility is in the method being checkable.Safety
150/150 green, 20 of them new. Golden vectors unmoved.
🤖 Generated with Claude Code