Property value assignment

I am not understanding why the first block of the code is valid but not the latter:

var e = new Exception()
{
    Data =
    {
        {"a","a"}
    }
};

Compile-time error here:

e.Data =
{
    {"a","a"}
};

Solution 1:

The other answer isn't 100% accurate. Yes, Data is a readonly property. However, a readonly (i.e., getter-only) property can only be given an initial/default value (in which case, the value is assigned to the compiler-generated backing field), but it cannot be assigned to; period. Not in the object initializer, not in the constructor, and not anywhere else.

Note that readonly fields, on the other hand, can be assigned to in the constructor.

In order to assign to a property, it must have a setter (e.g., set;, private set;, etc.) and if you're using C# 9.0 or above, you could use an init-only setter (i.e., init;) which will allow you to assign to the property in the object initializer but not after that.

As long as the property doesn't have a setter, you can never directly change its value. To prove this, try setting Data to null in the object initializer:

var e = new Exception()
{
    Data = null  // Nope, compiler won't allow it because Data is readonly
};

So, why does it work in the first block?

That's because you're initializing Data using a Collection Initializer. When you do that, the compiler doesn't actually assign a value to the property. Instead, it calls the Add() method. You can see this for yourself here. The first (i.e., working) block is translated by the compiler to:

Exception e = new Exception();
e.Data.Add("a", "a"); // <-- Ah! `Data` was never assigned to.

..which, by the way, would normally throw a NullReferenceException (because we're calling the Add() method on a property that should be null). But if you look at the source code of Exception.Data, you see that its backing field is always initialized in the getter so that it's never null:

public virtual IDictionary Data { 
    [System.Security.SecuritySafeCritical]  // auto-generated
    get {
        if (_data == null)
            if (IsImmutableAgileException(this))
                _data = new EmptyReadOnlyDictionaryInternal();
            else
                _data = new ListDictionaryInternal();
        
        return _data;
    }
}

Solution 2:

Well Data property has been declared as readonly property:

public virtual System.Collections.IDictionary Data { get; }

That's why you can initialize it (i.e. set in constructor)

  // Create an instance, initialize `Data` with { "a", "a" }
  Exception e = new Exception() {
    Data = { { "a", "a" } }
  };

But you can't set it:

 // Trying to set Data as { { "a", "a" } };
 e.Data = { { "a", "a" } }; // <- Compile time error here

Error CS0200 Property or indexer 'Exception.Data' cannot be assigned to -- it is read only (bold is mine, Dmitry Bychenko)

Please note, that you can just add required items into Data:

  Exception e = new Exception();

  // Add {"a", "a"} to existing Data
  e.Data.Add("a", "a");