18

I wonder why it is not possible to do the following:

struct TestStruct
{
    public readonly object TestField;
}

TestStruct ts = new TestStruct {
    /* TestField = "something" // Impossible */
};

Shouldn't the object initializer be able to set the value of the fields ?

HerpDerpington
  • 3,751
  • 4
  • 27
  • 43

8 Answers8

14

Object Initializer internally uses a temporary object and then assign each value to the properties. Having a readonly field would break that.

Following

TestStruct ts = new TestStruct 
{
     TestField = "something";
};

Would translate into

TestStruct ts;
var tmp = new TestStruct();
tmp.TestField = "something"; //this is not possible
ts = tmp;

(Here is the answer from Jon Skeet explaining the usage of temporary object with object initalizer but with a different scenario)

Community
  • 1
  • 1
Habib
  • 219,104
  • 29
  • 407
  • 436
  • Where do you get this temporary object business? – siride Aug 22 '13 at 15:54
  • I remember reading an answer from Jon Skeet about the object not getting disposed when initialized through object initializer. Let me find the link – Habib Aug 22 '13 at 15:55
  • I decompiled some of my code that uses initializers and I don't see any temporary objects. – siride Aug 22 '13 at 15:57
  • @siride, I don't think de-compiler would show that, the temporary object is created on the stack. This is how it is managed, I can't find a reference other than answer from Jon Skeet *(if you consider him as a reference)* – Habib Aug 22 '13 at 16:00
  • you are meaning to say that the JIT creates it but it isn't in the IL. I suppose I could look at disassembly when running, but I can't be bothered at this point. In any case, aside from the finalizer/GC issue, the temporary object is entirely irrelevant. – siride Aug 22 '13 at 16:01
  • 2
    @siride, I don't think its irrelevant. ReadOnly fields should either be assigned a value in Constructor or with declaration. The reason it is not allowed in object initializer is that it internally uses a temporary object and then assign each property a value. This would break the simple C# concept of not assigning a read-only field before usage. – Habib Aug 22 '13 at 16:08
  • 2
    no, the real reason it isn't allowed is that the object is already constructed and once an object is constructed, no readonly fields can be changed. Note that even with temp objects, it's the temp object that is having its properties/fields set, so the problem clearly has nothing to do with temp objects. – siride Aug 22 '13 at 17:20
13

readonly means that the field can only be set in the constructor (or in a field initializer). Properties specified in the object initializer are set after the constructor has returned. That is,

TestStruct ts = new TestStruct {
    TestField = "something"
};

is basically equivalent to

TestStruct ts = new TestStruct();
ts.TestField = "something";

(In a Debug build, the compiler may use a temporary variable, but you get the idea.)

Michael Liu
  • 52,147
  • 13
  • 117
  • 150
  • Well, but your example is not exactly how it gets compiled check out my [answer](http://stackoverflow.com/a/18385343/2530848) – Sriram Sakthivel Aug 22 '13 at 15:55
  • @SriramSakthivel: The C# specification defines an object initializer in terms of a temporary variable (hence the "basically equivalent" in my answer). However, in a Release build, the compiler is smart enough to elide the temporary variable. – Michael Liu Aug 22 '13 at 16:01
  • This is the right answer. The rule is that you can't assign readonly fields after the object is constructed. This can readily be seen if you try to do the same thing with the regular syntax instead of the initializer. The temporary object is a red-herring. – siride Aug 22 '13 at 19:47
12

C# 9 Init-Only Properties, despite the name, will allow the initializer syntax to be able to set readonly fields as well.

Here are the relevant parts copied from the links.

Init-only properties

Here's a simple example of object initializer.

new Person
{
    FirstName = "Scott",
    LastName = "Hunter"
}

The one big limitation today is that the properties have to be mutable for object initializers to work: They function by first calling the object’s constructor (the default, parameterless one in this case) and then assigning to the property setters.

Init-only properties fix that! They introduce an init accessor that is a variant of the set accessor which can only be called during object initialization:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

With this declaration, the client code above is still legal, but any subsequent assignment to the FirstName and LastName properties is an error.

Init accessors and readonly fields

Because init accessors can only be called during initialization, they are allowed to mutate readonly fields of the enclosing class, just like you can in a constructor.

public class Person
{
    private readonly string firstName;
    private readonly string lastName;
    
    public string FirstName 
    { 
        get => firstName; 
        init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
    }
    public string LastName 
    { 
        get => lastName; 
        init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
    }
}
Default
  • 684
  • 9
  • 19
2

This is not possible. since readonly fields cannot be assigned from other than Constructor or Field Initializer.

What you show is actually object initializer. It is just a syntatic sugar, gets comiled into something like this

TestStruct ts;
TestStruct ts1 = new TestStruct();
ts1.TestField = value;
ts = ts1;

Is that clear why it doesn't compile?

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
1

I wonder why it is not possible to do the following:

Because the compiler cannot know for sure that the following code will be executed:

TestStruct ts = new TestStruct 
{
    TestField = "something"
};

You should initialize readonly members directly inline or inside the constructor.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
1

From MSDN:

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.

So it's simply not (yet) possible since object initializers are just post-creation assignments.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

Because object initializer is just a short way of initializing:

TestStruct ts = new TestStruct {
  TestField = "something";
};

is the same to (compiler will translate the above to this):

TestStruct ts = new TestStruct();
ts.TestField = "something";//this is of course not allowed.
King King
  • 61,710
  • 16
  • 105
  • 130
-1

I ran across an interesting "exception" to this, in the case where the readonly field extends CollectionBase.

Here's the code:

using System.Collections;

namespace ReadOnly
{
    class Program
    {
        static void Main(string[] args)
        {
            Foo foo1 = new Foo()
            {
                Bar = new Bar()  // Compile error here - readonly property.
                {
                    new Buzz() { Name = "First Buzz" }
                }
            };

            Foo foo2 = new Foo()
            {
                Bar = // No Compile error here.
                {
                    new Buzz { Name = "Second Buzz" }
                }
            };
        }
    }

    class Foo
    {
        public Bar Bar { get; }
    }

    class Bar : CollectionBase
    {
        public int Add(Buzz value)
        {
            return List.Add(value);
        }

        public Buzz this[int index]
        {
            get { return (Buzz)List[index]; }
            set { List[index] = value; }
        }
    }

    class Buzz
    {
        public string Name { get; set; }
    }
}

Foo1 is how I initially tried to do it (all these classes came from an external library so we didn't know at first that Bar was readonly). Got the compile error. Then accidentally I retyped it like foo2, and it worked.

After decompiling the dll and seeing that Bar extended CollectionBase, we realized that the second syntax (foo2) was invoking the Add method on the collection. So, in the case of collections, while you can't set a read only property, you can invoke the Add method, via object initializers.

steverb
  • 1,415
  • 1
  • 10
  • 12
  • 1
    You're not using an object initializer, you're using a collection initializer, which is a completely different thing. You're also not assigning a value to a read only field through your collection initializer, so you're still not "getting around" the issue in any way. – Servy Nov 17 '16 at 17:14
  • 1
    Fair point - I should have used the term collection initializer, not object initializer. And, I see what you mean now about the assignment of the read-only field. What I am missing in the code sample is the constructor for Foo. – steverb Nov 18 '16 at 17:07
  • The class should look like: class Foo { public Bar Bar { get; } public Foo() { Bar = new Bar(); } } So Bar gets assigned during construction of Foo. Then in our collection initialization we are just adding to the collection. Thanks for pointing this out. – steverb Nov 18 '16 at 17:09
  • But the OP doesn't have a collection. He's wondering why you can use an object initializer on a read-only *non-collection* field and it still compiles. Saying that you can use a collection initializer isn't relevant to answering that question. – Servy Nov 18 '16 at 17:15