17

The following does not compile.

public class A
{
    private readonly int i;

    public A()
    {
        void SetI()
        {
            i = 10; 
        }

        SetI();
    }
}

It fails with this error:

CS0191 A readonly field cannot be assigned to (except in a constructor or a variable initializer)

Technically are we not in the constructor still, since the visibility of the local function is limited, so I'm wondering why this does not compile.

Kris Harper
  • 5,672
  • 8
  • 51
  • 96
ilias
  • 2,620
  • 2
  • 27
  • 37
  • If you could do this then the variable could no longer be called readonly. By definition, it can only be set once, during construction or with an initializer. – Andy G Mar 15 '19 at 11:34
  • Probably the local method could be called using reflection, which might allow modification of the readonly field after construction, so that's why they forbid it..? – Thomas Hilbert Mar 15 '19 at 11:35
  • 2
    There are two obvious workarounds if you do want this, incidentally: either make `SetI` return the desired value instead of assigning it directly, or use an `out` parameter. Both involve still doing the actual assignments in the constructor itself, of course. Because local functions are documented to be private methods, just with different scope rules, this is somewhat consistent. It would not technically be possible for C# to support this without a (rather obnoxious) change in the runtime to establish a "readonly path" of callers -- not really worth it. – Jeroen Mostert Mar 15 '19 at 11:54
  • I think the constructor could pass the `readonly` field directly as a `ref` or `out` parameter, thus allowing the function receiving it to perform the store to that field (the way you describe it makes it sound as though the constructor function would have to pass a temporary and then copy it to the `readonly` field itself). – supercat Mar 15 '19 at 16:46

1 Answers1

24

The compiler turns the SetI local function into a separate class-level method. Since this separate class-level method is not a constructor, you are not allowed to assign to readonly fields from it.

So the compiler takes this:

public class A
{
    private readonly int i;

    public A()
    {
        void SetI()
        {
            i = 10; 
        }

        SetI();
    }
}

and turns it into this:

public class A
{
    private readonly int i;

    public A()
    {
        <.ctor>g__SetI|1_0();
    }

    [CompilerGenerated]
    private void <.ctor>g__SetI|1_0()
    {
        i = 10;
    }
}

(SharpLab. I left off the readonly so it would compile.)

As you can see, it's trying to assign i from the method <.ctor>g__SetI|1_0(), which isn't a constructor.

Unfortunately the C# 7.0 language specification hasn't yet been published, so I can't quote it.

Exactly the same happens if you try and use a delegate:

public class A
{
    private readonly int i;

    public A()
    {
        Action setI = () => i = 10;

        setI();
    }
}

Gets compiled to:

public class A
{
    private readonly int i;

    public A()
    {
        Action action = <.ctor>b__1_0;
        action();
    }

    [CompilerGenerated]
    private void <.ctor>b__1_0()
    {
        i = 10;
    }
}

(SharpLab, again without the readonly.)

... which likewise fails to compile.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • Thanks @canton7, I am looking forward to the c#7/8 language specification when/if it arrives. – ilias Mar 15 '19 at 15:54