22

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 ?

0

8 Answers 8

16

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)));
    }
}
Sign up to request clarification or add additional context in comments.

Comments

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)

7 Comments

Where do you get this temporary object business?
I remember reading an answer from Jon Skeet about the object not getting disposed when initialized through object initializer. Let me find the link
I decompiled some of my code that uses initializers and I don't see any temporary objects.
@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.
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.
|
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.)

3 Comments

Well, but your example is not exactly how it gets compiled check out my answer
@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.
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.
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?

Comments

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.

Comments

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.

Comments

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.

Comments

-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.

4 Comments

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.
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.
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.
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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.