0

As we know the static constructor is called the first time the class is called.

We now have a class:

static class Demo
{
    private readonly static int _intValue;

    static Demo ()
    {
        _intValue = 5;
    }
}

The constructor will be called when the Demo is accessed. The value 5 will be assigned to the _intValue readonly field. After that, we can once again set the value through reflection:

typeof (Demo)
  .GetField ("_ intValue", BindingFlags.Static | BindingFlags.NonPublic)
  .SetValue (null, 567);

Why value 5 can be overwritten? Isn't it already initialized in the constructor? Why isn't System.FieldAccessException thrown?

Tested in NET Core 3.1. Full example https://dotnetfiddle.net/4DsJ6H

UPDATED: I found a way to overwrite a read-only static field even after it was requested once. It is based on method generation via IL. Actually, in addition to the original question - how does this workaround work?

https://dotnetfiddle.net/oYaf5t

KregHEk
  • 442
  • 6
  • 12
  • 2
    Reflection lets you do things you normally can't with ordinary C# code or even the compiled IL code. You can call private methods from outside of their declaring type, for instance. This appears to be a similar case. – Joe Sewell Aug 14 '20 at 20:38
  • @JoeSewell why you did not mention it as an answer? – Sh.Imran Aug 14 '20 at 20:46
  • @JoeSewell I am interestiong how this assignment is done in details, that it gets around the limitation of readonly fields? Is this provided by the environment and why? Or is this just a feature of .NET Core? For example, in NET Framework 4.7 a static readonly field will not be found at all through reflection. Is this somehow related? I just have no idea what to get hold of in order to better understand the behavior described in the question. – KregHEk Aug 14 '20 at 20:54
  • "in NET Framework 4.7 a static readonly field will not be found at all through reflection" - that's not true at all; and in .net core 3.1 you **cannot** rely on being able to s static ReadOnly fields via reflection, because it would impact certain new JIT optimisations (this might only be for ref-types (classes) though, for devirtualization) – Marc Gravell Aug 14 '20 at 21:19
  • @MarcGravell https://dotnetfiddle.net/4DsJ6H This example overwrites the value of a read-only static field. If I am missing something, please point out the error. – KregHEk Aug 14 '20 at 21:23
  • 1
    Here is a deeper [discussion](https://github.com/dotnet/runtime/issues/11571) of what @MarcGravell is getting at. – Sean Skelly Aug 14 '20 at 21:26

1 Answers1

1

Reflection is already breaking all the rules, including accessibility and mutability; it is effectively as powerful as unsafe: and just like unsafe: if something goes wrong, it is self inflicted and the runtime will laugh at you.

Note that in .NET Core, the runtime will sometimes stop you doing this, because of JIT optimisations that would become invalid if you did. But if it doesn't here: fine.

Note: you used to be able to change string.Empty via reflection. Imagine how well that ended :)

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • "Note: you used to be able to change string.Empty" I asked the question above while replacing TaskScheduler.Default with my scheduler. Of course, not in prod :) – KregHEk Aug 14 '20 at 21:45
  • Am I correct in understanding that in this simple example, the runtime allowed a read-only static field to be overwritten because it did the JIT correctly? If so, can you give an example of when this cannot be done? – KregHEk Aug 14 '20 at 22:04
  • @KregHEk Sean Skelly already linked you one. Perhaps it is relevant that your example is a value-type, so no devirtualization needs to be (or can be) done. – Marc Gravell Aug 14 '20 at 22:54
  • 1
    @KregHEk Can't help myself; here's another link with an example, this time to [a SO answer](https://stackoverflow.com/a/2761447/3791245). tl;dr: the combination of having both a static constructor _and_ a static readonly field causes subtle differences in the IL for when the field is set, compared to only having a static readonly field. But do read through it and pay attention to "beforefieldinit". – Sean Skelly Aug 14 '20 at 23:34