C# object initialization of read only collection properties
This:
MyClass c = new MyClass
{
StringCollection = { "test2", "test3" }
};
is translated into this:
MyClass tmp = new MyClass();
tmp.StringCollection.Add("test2");
tmp.StringCollection.Add("test3");
MyClass c = tmp;
It's never trying to call a setter - it's just calling Add
on the results of calling the getter. Note that it's also not clearing the original collection either.
This is described in more detail in section 7.6.10.3 of the C# 4 spec.
EDIT: Just as a point of interest, I was slightly surprised that it calls the getter twice. I expected it to call the getter once, and then call Add
twice... the spec includes an example which demonstrates that.
Readonly field in object initializer
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)
Object initializer for readonly properties in c#
If you read the actual compiler errors (and the docs for collection initializers), you'll find that collection initializers are merly syntactic sugar for Add()
calls:
CS1950: The best overloaded collection initalizer method
System.Collections.Generic.ICollection<Bar>.Add(Bar)
has some invalid argumentsCS1503: Argument
#1
cannot convertSystem.Collections.Generic.IEnumerable<Bar>
expression to typeBar
So the syntax SomeCollection = { someItem }
will be compiled to SomeCollection.Add(someItem)
. And you can't add IEnumerable<Bar>
to a collection of Bar
s.
You need to manually add all items:
foreach (bar in items.Select(s => new Bar { Prop = s }))
{
foo.Bars.Add(bar);
}
Or, given shorter code is your goal, do the same in Foo2
's constructor:
public class Foo2
{
public ICollection<Bar> Bars { get; }
public Foo2() : this(Enumerable.Empty<Bar>()) { }
public Foo2(IEnumerable<Bar> bars)
{
Bars = new List<Bar>(bars);
}
}
Then you can initialize Foo2 like this:
var foo = new Foo2(items.Select(...));
For a funny abuse of the collection initializer syntax as supposed by @JeroenMostert, you could use an extension method:
public static class ICollectionExtensions
{
public static void Add<T>(this ICollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
{
collection.Add(item);
}
}
}
Which allows this:
public class Foo
{
public ICollection<string> Bar { get; } = new List<string>();
}
var foo = new Foo
{
Bar = { new [] { "foo", "bar", "baz" } }
};
But that's just nasty.
Initialize get-only collection in object initializer from existing collection
No. You can't assign this read-only property from a collection initializer. It is read-only after all.
TheList = { "A", "B" }
works since it calls Add
on TheList
(once for each item added), it doesn't create and assign a new instance, which it is not allowed to.
TheList = { existingList }
doesn't work since there is a typing issue (TheList = { existingList[0] }
does work).
The best option you have it to create a constructor parameter and drop your idea of using collection initializers for something it isn't fit for.
C# Class without explicit constructor and read only property
This somewhat confusing object initializer syntax retrieves the collection with the get
accessor and uses the public Add
method on the collection. It works because the compile-time type of the property IList<Person>
has (inherits) a public Add
method with a compatible signature (takes in a Person
argument).
Add to a readonly collection in a constructor?
Try this. It is definitely possible to add elements on construction
var node = new Node
{
Children =
{
new Node(),
new Node()
}
};
C# object initializer will initialize read only properties, but for non-primitive types only
var baz = new Foo { Bar = { Prop = "Hello World!" } }; // legal
This is not an assignment to Bar
. It is essentially:
var tmp = new Foo();
tmp.Bar.Prop = "Hello World!";
var baz = tmp;
At no point is .Bar
assigned to.
Conversely, however:
var buzz = new Foo { Primitive = 1 };
is:
var tmp = new Foo();
tmp.Primitive = 1;
var buzz = tmp;
which does assign to .Primitive
.
Why can I use an anonymous collection initializer with a read-only auto-property while I can't use an object initializer
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.
In C#9, how do init-only properties differ from read-only properties?
An init
accessor is identical to a set
accessor in implementation in almost all areas, except that it is flagged in a certain manner that makes the compiler disallow usage of it outside of a few specific contexts.
By identical I really do mean identical. The name of the hidden method that is created is set_PropertyName
, just as with a set
accessor, and using reflection you can't even tell them apart, they will appear to be identical (see my note about this below).
The difference is that the compiler, using this flag (more on this below) will only allow you to set a value to the property in C# (more on this below as well) in a few specific contexts.
- From a constructor of the type, or a derived type
- From an object initializer, ie.
new SomeType { Property = value }
- From within the construct with the new
with
keyword, ie.var copy = original with { Property = newValue }
- From within the
init
accessor of another property (so oneinit
accessor can write to otherinit
accessor properties) - From attribute specifiers, so you can still write
[AttributeName(InitProperty = value)]
Outside of these, which basically amounts to normal property assignment, the compiler will prevent you from writing to the property with a compiler error like this:
CS8852 Init-only property or indexer 'Type.Property' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
So given this type:
public class Test
{
public int Value { get; init; }
}
you can use it in all these ways:
var test = new Test { Value = 42 };
var copy = test with { Value = 17 };
...
public class Derived : Test
{
public Derived() { Value = 42; }
}
public class ViaOtherInit : Test
{
public int OtherValue
{
get => Value;
init => Value = value + 5;
}
}
but you can not do this:
var test = new Test();
test.Value = 42; // Gives compiler error
So for all intents and purposes this type is immutable, but it now allows you to more easily construct an instance of the type without tripping into this immutability issue.
I said above that reflection doesn't really see this, and note that I learned about the actual mechanism just today so perhaps there is a way to find some reflection code that can actually tell the difference. The important part is that the compiler can see the difference, and here it is.
Given that the type is declared as:
public class Test
{
public int Value1 { get; set; }
public int Value2 { get; init; }
}
then the generated IL for those two properties will look like this:
.property instance int32 Value1()
{
.get instance int32 UserQuery/Test::get_Value1()
.set instance void UserQuery/Test::set_Value1(int32)
}
.property instance int32 Value2()
{
.get instance int32 UserQuery/Test::get_Value2()
.set instance void modreq(System.Runtime.CompilerServices.IsExternalInit) UserQuery/Test::set_Value2(int32)
}
You can see that the Value2
property setter (the init
method) has been tagged/flagged (unsure if these are the right words, I did say I learned this today) with the modreq(System.Runtime.CompilerServices.IsExternalInit)
type which tells the compiler this method is not your uncle's set accessor.
This is how the compiler will know to treat this accessor method differently than a normal set
accessor.
Given @canton7's comments on the question this modreq
construct also means that if you try to use a library compiled with the new C# 9 compiler in an older C# compiler it will not consider this method. It also means you won't be able to set the property in an object initializer but that is of course only available in C# 9 and newer compilers anyway.
So what about reflection for setting the value? Well, turns out reflection will be able to call the init
accessor just fine, which is nice because this means deserialization, which you could argue is a kind of object initialization, will still work as you would expect.
Observe the following LINQPad program:
void Main()
{
var test = new Test();
// test.Value = 42; // Gives compiler error
typeof(Test).GetProperty("Value").SetValue(test, 42);
test.Dump();
}
public class Test
{
public int Value { get; init; }
}
which produces this output:
and here's a Json.net example:
void Main()
{
var json = "{ \"Value\": 42 }";
var test = JsonConvert.DeserializeObject<Test>(json);
test.Dump();
}
which gives the exact same output as above.
Related Topics
Converting String Format to Datetime in Mm/Dd/Yyyy
Run .Exe Executable File in Azure Function
Datagridview Using Sortablebindinglist
Manual Editing of *.Designer.Cs File
Should I Take Ilogger, Ilogger<T>, Iloggerfactory or Iloggerprovider for a Library
Automatically Update Version Number
Best Hashing Algorithm in Terms of Hash Collisions and Performance for Strings
The Remote Certificate Is Invalid According to the Validation Procedure
Working with Incredibly Large Numbers in .Net
ASP.NET How to Stream File to User
Compile-Time and Runtime Casting C#
Get the Final Generated HTML Source Using C# or Vb.Net
Wpf Openfiledialog with the Mvvm Pattern
In C# Differencebetween a Destructor and a Finalize Method in a Class
Is Async/Await Suitable for Methods That Are Both Io and CPU Bound