Home > Back-end >  Why can I use an anonymous collection initializer with a read-only auto-property while I can't
Why can I use an anonymous collection initializer with a read-only auto-property while I can't

Time:08-20

Consider the following class with a read-only (or getter-only) property ClientPermissions :

internal class Client
{
    public string? ClientId { get; set; }

    public HashSet<string> ClientPermissions { get; } = new(StringComparer.Ordinal);

    public HashSet<string> ClientTokens { get; set; } = new(StringComparer.Ordinal);

}

It seems I can't assign an object during construction to the read-only auto-property ClientPermissions while I can assign it values with an anonymous collection initializer

SO 5646285 gives a hint that for the object initializer the dotnet compiler actually compiles this into using object creation and then addition of the values.

Ok.. but why can I use an anonymous collection initializer than with this read-only auto-property?

        // Works - no complaints from compiler when I use collection initializer on read-only auto-property ClientPermissions
        var sc1 = new Client() { ClientId = "c1", ClientPermissions = { "a1", "b1" }, ClientTokens = { "t1", "t2" } };

        // Works - no complaints from compiler when I use collection initializer on read-only auto-property and object initializer on normal/full auto-property
        var sc2 = new Client() { ClientId = "c2", ClientPermissions = { "a1", "b1" }, ClientTokens = new HashSet<string>{ "t1", "t2" } };

        // DOES NOT COMPILE - Compiler complains with a CS0200: Property or indexer '...' cannot be assigned to -- it is readonly
        // auto-initialize syntax 
        var sc3 = new Client() { ClientId = "c3", ClientPermissions = new HashSet<string> { "a1", "b1" }, ClientTokens = new HashSet<string> { "t1", "t2" } };

CodePudding user response:

This is calling the Add method on the object referenced by ClientPermissions:

ClientPermissions = { "a1", "b1" }

It is not assigning a new object to that property, hence why it is allowed.

This, conversely, is invalid, because you cannot assign a new object to that property after construction:

ClientPermissions = new HashSet { "a1", "b1" }

The relevant documentation is here.

CodePudding user response:

That hint you found is exactly the reason.

If you use a collection initialiser on the right hand side of ClientPermissions =, the setter of ClientPermissions is not called, as the post you linked says. The generated code is:

Client client = new Client();
client.ClientId = "c3";
client.ClientPermissions.Add("a1");
client.ClientPermissions.Add("b1");
HashSet<string> hashSet = new HashSet<string>();
hashSet.Add("t1");
hashSet.Add("t2");
client.ClientTokens = hashSet;

Whereas if you use = new HashSet<string> { ... }, then you are calling the setter of ClientPermissions, because the thing after the = (i.e. new HashSet<string> { ... }) is an expression. Note that this expression also has a collection initialiser inside it, that is processed separately.

Client client = new Client();
client.ClientId = "c3";
HashSet<string> hashSet = new HashSet<string>();
hashSet.Add("a1");
hashSet.Add("b1");
client.ClientPermissions = hashSet; // setter called here!
HashSet<string> hashSet2 = new HashSet<string>();
hashSet2.Add("t1");
hashSet2.Add("t2");
client.ClientTokens = hashSet2;

Think of it like this: the ClientPermissions = new HashSet<string> { ... } case is treated like any other Property = expression initialiser in an object initialiser. On the other hand, the ClientPermissions = { ... } case is the "special" syntax that you can only do in object initialisers and it is where Add is called, rather than assigning the RHS to the LHS.

  •  Tags:  
  • c#
  • Related