Build1 distinct publisher3 min readPublished
The combination of InlineArray and an explicit StructLayout.Size still compiles on .NET 10, and then throws TypeLoadException the first time the type loads, which can be a cold interop path inside a dependency you did not build.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The attribute is the layout description. The runtime takes the struct's single field and repeats it for the length passed to InlineArrayAttribute, and the InlineArrayAttribute documentation treats that attribute as the only mechanism that should define the total repeated shape [17][4]. Eight ints, 32 bytes on a normal .NET 10 target [5]. Divide it out and each element is 4 bytes [16], which is exactly the Size = 4 the dev.to writeup ends up putting on an element struct in its second fix [13]. So in this example the two declarations agree. .NET 10 refuses the type anyway, because any interpretation of duplicate size information would be ambiguous, where earlier runtimes allowed implementation-specific behaviour [3][2].
The audit in the writeup has two steps [15]. Scanning source and generated interop code for types carrying both attributes is the cheap one, and it misses the case that hurts: the shape can already be baked into a referenced assembly, where nothing in your build mentions it, and the first code to force the load may be reflection, interop registration, or serialization [7]. Hence step two, loading every relevant plugin and interop assembly in a .NET 10 test process [15]. A successful build tells you the compiler agreed with you; the loader gets a separate vote.
Deleting the Size attribute makes the exception go away and takes an interop assumption with it, which is why the writeup declines to do that blindly [11]. The number meant one of two things. If 32 bytes described the whole native buffer, leave InlineArray(8) on the inner struct and put Size = 32 on a wrapper whose only field is that array [12]. If it described one native element, put the size on the element type and let InlineArray(8) repeat it [13]. Guessing downward has its own failure mode: the StructLayout size reference warns that an explicit size must be at least as large as the type's fields [14].
Whether the 32 transfers to your codebase depends on where it came from. In the sample it is sizeof(int) times eight and nothing more [16]. In real interop the byte count usually came from a C header with its padding included, so the assertion worth copying is the one the sample automates: Unsafe.SizeOf<T>() and fixed values proving each replacement is still 32 bytes and still supports all eight indexed values, with identical output on repeated runs [9].
The best engineering in the post is the fixture. Rather than ship a deliberately broken assembly, it builds the invalid metadata at runtime with Reflection.Emit: DefineType with typesize 32, an InlineArrayAttribute(8) custom attribute, one private int field named _element0, and CreateTypeInfo as the call that throws on .NET 10 [8]. Keeping the bad shape behind that loader boundary is what makes the exception catchable at all, because a broken type referenced directly across a test executable can be forced while the runtime compiles a method, before control reaches the intended try block [10]. That detail generalises past this rule. Any test that asserts on a load-time failure needs the failure isolated from the code doing the asserting.
Ranked by verification strength, evidence, and original report placement.
Microsoft documents this as a .NET 10 binary compatibility change; earlier runtimes allowed implementation-specific behaviour for the combination.
.NET 10 rejects the combination when the type is loaded because any interpretation of the duplicate size information would be ambiguous.
An inline array already defines its storage: the runtime repeats the struct's single field for the length supplied to InlineArrayAttribute.
A struct declared [InlineArray(8)] with one private int field occupies 32 bytes on a normal .NET 10 target.
Adding [StructLayout(LayoutKind.Sequential, Size = 32)] to the same inline array type creates two competing descriptions of its layout.
The InlineArrayAttribute documentation describes the attribute as representing sequentially replicated storage, and it should be the only mechanism defining the inline array's total repeated shape.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · September 1, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Console JSON is a schema: .NET 10 dropped the duplicate State.Message, and parsers go quiet1 distinct publisher
build
MTP 2.3 writes TRX as tests finish, so a dead test host no longer erases the evidence1 distinct publisher
build
.NET 10 fails restore on a versionless PackageReference, and the reflexive fix breaks CPM1 distinct publisher
build
VlvRequestControl throws on the malformed target that .NET 9 used to rewrite1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Executable mechanism, borrowed provenance
The loader rule is not merely asserted here, it is run: metadata built with Reflection.Emit throws on CreateTypeInfo, and both replacements are then measured at 32 bytes with every one of the eight slots read back. The soft spot is attribution and reach — that Microsoft filed this as a .NET 10 binary compatibility change arrives only as the author's summary, and the harder guarantees the piece declines to claim, native field order and packing across architectures, stay untested.
Rule visible, victims invisible
We can see the rule and one deliberate demonstration of it; we cannot see anyone hitting it. No affected package, no plugin host that crashed on upgrade, no count of shipped types carrying both attributes — which is precisely the number that would tell a team whether this is a footnote or a migration item.
Sold below its stakes
The promise is a TypeLoadException and a TypeLoadException is delivered; there is no 'breaking change nobody is talking about' framing, and the author volunteers where his sample stops. If anything the consequence is undersold — a value type that only loads on a cold interop path is exactly the failure that survives a clean build and a smoke test, and that observation is made once, in passing, then left alone.
Byline credit, no paymaster
A practitioner writing under his own name with a merged pull request to point at: the upside is reputational, complete with a sign-off and a question thrown to readers. Nothing sells a product, knocks a competing runtime, or depends on anyone upgrading. The pressure to watch is the tutorial's gravitational pull toward a clean two-option fix, not a sponsor.
One voice, unusually checkable
Everything traces to a single dev.to post, which ordinarily caps how far we will go. This one holds up better than most because its central assertion is falsifiable in about a minute on any .NET 10 machine. Where our certainty thins is the material we cannot re-run: the documentation attribution and the advice about trimming, ahead-of-time builds, and plugin load order, none of which has a second source behind it.