Skip to content
QIO journal

Technology7 min read

Reflection is 36 times slower: reach a private field in 0.25 nanoseconds

Reading a private field through reflection takes 8.96 nanoseconds and allocates 24 bytes on the heap. The same access without reflection takes 0.25 nanoseconds and allocates nothing: a 35.8 times difference, and on .NET 8 it reached 108. The technique is called UnsafeAccessor, it arrived in .NET 8, works without code generation and is used inside the class library itself.

Reflection is 36 times slower: reach a private field in 0.25 nanoseconds

What it looks like in code

The situation is common: a class from someone else's library, a package or the platform itself cannot be edited, yet a private field has to be reached. The habitual route is to get the field description through GetField with non-public binding flags and call GetValue.

The .NET 8 approach looks different. A method is declared as extern static with no body, supplied by the runtime: an UnsafeAccessor attribute with the Field kind and the field name, returning a reference to the field. Because it returns by reference, one declaration covers both reading and writing.

Techniques like this belong in code after profiling, not before it. We ask to see the measurement where field access is actually visible in the profile. Usually the time turns out to be going into database queries, and the private field has nothing to do with it.

What the benchmark showed

The author ran the comparison on four machines: an Intel Core i9-10900KF, an AMD Ryzen 9 5950X, an Intel Xeon W-2255 and a dual-socket Xeon Silver 4314 system. All x64, with the .NET 8, 9 and 10 runtimes in a single BenchmarkDotNet run. Six approaches were compared, and everything that could be prepared in advance was prepared: the field description looked up once, the expression tree compiled, the IL-emitted method built.

The result: UnsafeAccessor runs in the same time as a direct read of the field inside the class itself. Reflection with a ready field description is 2.9 to 7.8 times slower, and reflection that looks the field up on every access is 14.8 to 40.2 times slower. The expression tree and the IL-emitted method sit in between.

Two bars: one very short, one many times taller
Direct read and UnsafeAccessor against reflection with a field lookup.

Why it is that fast

Reflection works at runtime. The field description stores the offset, the type and the access rights, and every GetValue call checks whether access is allowed and converts the result to object — hence both the time and those 24 bytes of boxing on the heap.

UnsafeAccessor works when the method is compiled. The runtime resolves the field once, the first time it compiles the accessor, and inlines an ordinary read by offset. After that the same machine code runs as for a direct field access, so there is simply no difference left to measure.

Where it goes wrong

The price of the speed is the loss of several checks, and three traps are worth knowing in advance.

  • The field name is a string, and the compiler will not catch a typo: the build succeeds and the first call throws MissingFieldException. Reflection returns null from GetField in the same situation, which can be checked earlier.
  • The lookup happens only in the type given as the first argument: the runtime does not walk base types, so a field declared in a parent cannot be reached this way.
  • For a struct the first argument must be declared by reference. Otherwise the method receives a copy and a write to the field is lost silently.

When it is actually needed

The honest answer: less often than one would like. If a field is private, the library author usually had a reason, and reaching into it means accepting that the next version may break your code without a word.

The sensible cases are narrow: serialization, frameworks working with foreign objects, tests that need to inspect internal state, and hot code where reflection already showed up in the profile. Everywhere else, look for a public API first, because a nanosecond only matters across millions of calls.

The takeaway

UnsafeAccessor is a rare case where the faster route is also the simpler one: a single attribute instead of caching field descriptions and emitting code. But it removes exactly the checks that made reflection safe, so it belongs where the cost of access is genuinely visible in measurements.

Sources

  1. A private field in 0.25 nanoseconds, Habr
  2. UnsafeAccessorAttribute, Microsoft Learn
  • .NET
  • UnsafeAccessor
  • Reflection
  • BenchmarkDotNet
  • C#

Follow the journal

New pieces on websites, SEO and AI come out in the QIO journal. Follow in Google, by RSS or in Telegram to get them first.

Read next

How can we help?
Discuss a project