26

Why can't I initialize readonly variables in a initializer? The following doesn't work as it should:

class Foo
{
    public readonly int bar;
}

new Foo { bar=0; }; // does not work

Is this due to some technical limits of the CLR?

EDIT

I know that new Foo { bar=0; } is the same as new Foo().bar=0;, but is "readonly" enforced by the CLR, or is it just a compiler limitation?

codymanix
  • 28,510
  • 21
  • 92
  • 151
  • 3
    Following your latest edit, I have no idea what you're asking. Yes, `readonly` is enforced at run-time by the CLR. I don't see how it could be a compiler limitation. The other answers explain why what you're trying to do doesn't make any sense. – Cody Gray - on strike Dec 16 '10 at 19:09
  • @Cody Gray -- *language restriction*, not compiler limitation. The compiler just implements the language. –  Dec 17 '10 at 01:40
  • @pst: Uh, I said "I don't see how it could be a compiler limitation." I agree with you. – Cody Gray - on strike Dec 17 '10 at 01:43

11 Answers11

20

The initializer is just syntactic sugar. When you write:

new Foo { bar=0; };

(Which, by the way, is a syntax error and should be this...)

new Foo { bar=0 }

what's actually happening is:

var x = new Foo();
x.bar = 0;

Since the property is read-only, that second statement is invalid.

Edit: Based on your edit, the question is a little unclear. A readonly property is, by design, not settable. It's built at object construction. This is enforced by both the compiler and the runtime. (Admittedly, I haven't tested the latter, since it would take some trickery to get around the former.)

Keep in mind that there are two stages of "compilation." It's enforced when compiling the C# code into IL code, and it's enforced when compiling the IL code into machine code.

It's not a technical limit of the CLR, and it's working exactly as it should, given the explicit readonly declaration. After the object is constructed, you can't set a readonly property.

David
  • 208,112
  • 36
  • 198
  • 279
  • 1
    This answer just gets better and better. I've seen it twice now and gone to upvote it, when I realized I already had. – Cody Gray - on strike Dec 16 '10 at 20:20
  • @Cody Gray: Someone seems to disagree, there's a downvote. I'm pretty sure the question is a duplicate anyway, I think. It's hard to tell the intent of what's being asked. – David Dec 16 '10 at 21:33
  • There is no limitation if you use reflection, so e.g. `typeof(Foo).GetField("bar").SetValue(oldFooInstance, 42);` will change the value of the readonly field. – Jeppe Stig Nielsen Jun 22 '13 at 12:53
  • @JeppeStigNielsen: That would qualify as "some trickery to get around the compiler." Which in many cases is exactly what reflection is. One can also access private members with reflection, for example. – David Jun 22 '13 at 12:56
  • 1
    Absolutely! And it will likely make the object malfunction since the authors of `Foo` did not expect you to do that. But maybe the asker was interested in this, as a comment to your great answer. – Jeppe Stig Nielsen Jun 22 '13 at 13:15
  • 'new Foo { bar=0; };' is a syntax error, as one does not put semicolons inside initializer lists – Demur Rumed Jun 06 '16 at 14:47
  • @DemurRumed: Good catch, thanks! I guess I'd just copied/pasted from the original when I wrote this. I've updated the answer. – David Jun 06 '16 at 15:15
18

Allowing a readonly to be set in an initializer introduces contradictions and complications that can't be enforced at compile-time. I imagine the restriction is to avoid ambiguity. The big key is compile-time validation.

Imagine this:

class Foo
{
    public readonly int bar;
    Foo () {
      // compiler can ensure that bar is set in an invoked ctor
      bar = 0;
    }
}

// compiler COULD know that `bar` was set in ctor
// and therefore this is invalid
new Foo { bar = 0; }

Now, consider:

class Foo
{
    public readonly int bar;
    Foo () {
      // imagine case where bar not set in ctor
    }
}

// compiler COULD know that `bar` is not bound yet
// therefore, this COULD be valid
new Foo { bar = 0; }

// but this COULD be proved to never be valid
new Foo();

Imagine that both of the above cases are unified (say, "by compiler magic"), however, enter in generics:

T G<T> () where T : new
{
  // What in heck should happen *at compile time*?
  // (Consider both cases above.)
  // What happens if T (Foo) changes to include/not-include setting the
  // readonly variable in the ctor?
  // Consider intermediate code that invokes G<Foo>() and this other
  // code is NOT recompiled even though Foo is--
  //   Yet a binary incompatibility has been added!
  //   No thanks!
  return new T();
}
G<Foo>();

I believe the cases I have outlined show some complications of using a "dynamic" readonly approach and, at the end of the day, I believe it is merely a chosen language restriction (compilers implement languages) to enforce/allow compile-time validation.

7

C# 9.0 finally brings us init-only property setters:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/init

struct Point
{
    public int X { get; init; }
    public int Y { get; init; }
}

var p = new Point() { X = 42, Y = 13 };
codymanix
  • 28,510
  • 21
  • 92
  • 151
  • This is the correct answer for modern C#. – phoenix Dec 21 '21 at 18:53
  • I wish this would bubble up a bit towards the accepted answer. – dacabdi Apr 25 '22 at 19:25
  • Note, however, that these are no longer simple `readonly` _fields_, they are now _properties_, the produced IL will now be a function call instead of accessing the field. See https://sharplab.io/#v2:C4LglgNgNAJiDUAfAAgZgAQCcCmBDGA9gHYQCe6AzsJgK4DGw6ASnjAPImkBiY2EMAWABQAb2HoJ6NFlbEy6MEUYA1XBBrYA3OMk6J0lvg5kefGAApFjAG5qNASj3oxQyW/Sr12dAF50tr21XSQBfYTChYWkqWgZmVmNSAAVMAgAHbExgUmEXN2krDztvEXQAc2xgTQUiMCr0CIiojGQAJnQAFWwqXKcCpXj8U35zR2CJPPcJW0waqlwiOm8/ImwAd0H2TmGLABZW+yCp6dxZgI1fOeAFpYA6Tw0j4+QAdn9ip4kItz6MQsMYCl0plsqMnJMpjMrjdluhVhsAYkgRksuRSg9YfsGp93FDzrDFPNFth7h8nPk3vicY1hEA=== – Erunehtar Feb 07 '23 at 15:06
5

Since readonly variables must be initialized in constructor, and property initializers execute after the construction of object, that is not valid.

decyclone
  • 30,394
  • 6
  • 63
  • 80
3

Because an initializer is equivalent to

var foo = new Foo();
foo.bar=0;

It is a rewrite behind the scenes.

Albin Sunnanbo
  • 46,430
  • 8
  • 69
  • 108
2

What you're trying to do is this:

   class Foo
   {
        public readonly int bar;

        Foo(int b)
        {
             bar = b;  // readonly assignments only in constructor
        }
   }

   Foo x = new Foo(0);
1

Because you specified it is readonly. It does not make sense to specify that something is readonly then expect a write statement to work.

Joel Etherton
  • 37,325
  • 10
  • 89
  • 104
1

According to this page, the CLR default-constructs the object first before processing the initializer list, and you are therefore assigning to bar twice (once on default construction, once when the initializer is processed).

Platinum Azure
  • 45,269
  • 12
  • 110
  • 134
0

This is a result of the implementation of the readonly keyword. The quote below is taken from the MSDN reference for readonly:

The readonly keyword is a modifier that you can use on fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.

nabrond
  • 1,368
  • 8
  • 17
0

I know this isn't a direct answer to the poster's question, but the newer version of C# now allows for direct initialization from the property itself as follows.

public int bar { get; set; } = 0;

Again, I'm aware this doesn't solve the readonly issue as identified (and solved) above.

Mark Wise
  • 1
  • 1
  • Welcom to stackoverflow. If you know it doesn't answer the question, but you want to add addtional related information, then a comment is more appropriate. – belwood Jan 27 '19 at 18:23
-1

There is not much wrong in your code or assumptions with the exception maybe that it is an important feature of initializer lists to impose no sequence constraints (especially true for C++). The semicolon is a sequencing operator, consequently initializer lists are comma separated instead.

Unless you argue that specifications are correct by definition I believe that the language specification is wrong here. It partly breaks an important feature of the language which is the notion of readonly. The ambiguity problems mentioned in other answers have in my opinion one single underlying cause. Readonly is a very intrusive feature and going half way regarding const correctness is difficult to get right and more importantly, harmful to coding styles developed.

What you are looking for and probably found in the meantime are named arguments: https://stackoverflow.com/a/21273185/2712726 It is not what you asked for but gets near.

Also to be fair, I must add that there are very knowledgeable authors who will totally disagree with these views on const correctness that C++ developers often have. Eric Lippert, who admittedly has brilliant posts has written this (horrifying to C++ developers) statement: https://stackoverflow.com/a/3266579/2712726

Patrick Fromberg
  • 1,313
  • 11
  • 37