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.