I have seen this code example and it looks like it assigns an array initializer to a List. I thought it would not work but somehow it compiles. Is {} not an array initializer? Children is of type IList. How does it work without the "new List" before the curly braces?
var nameLayout = new StackLayout()
{
HorizontalOptions = LayoutOptions.StartAndExpand,
Orientation = StackOrientation.Vertical,
Children = { nameLabel, twitterLabel }
};
Edit: When I tried Children = new List<View>{ nameLabel, twitterLabel }
, the compiler gives this warning: "Property or indexer Layout.Children cannot be assigned to, it is read-only."
The code fragment is from Xamarin by the way: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/
That's a special case of a collection initializer.
In C#, the array initializer curly braces have been generalized to work with any collection class constructor.
Any class supports those if it implements
System.Collections.IEnumerable
and has one or moreAdd()
methods. Eric Lippert has a good post about this type of "pattern matching" in C#: What the compiler is doing here is what they call "duck typing", rather than conventional strongly typed OOP where the capabilities of a class are recognized based on inheritance and interface implementation. C# does this in a few places. There's a lot of stuff in that article I hadn't known.And then this compiles:
That's syntactic sugar for this:
You can play some pretty weird games with these. I don't know if it's a good idea to go all out (actually I do know -- it isn't), but you can. I wrote a command-line parser class where you can define the options via a collection initializer. It's got over a dozen overloads of
Add
with varying parameter lists, many of them generic. Anything the compiler can infer is fair game.Again, you can push this beyond diminishing returns to the point of feature abuse.
What you're seeing is an extension of the same initializer syntax, where it lets you do a collection initializer for a non-assignable member that the class itself already created:
And now...
{ 0, "Beringia" }
is a collection initializer for theFoo
instance thatBar
created for itself; it's syntactic sugar for this:The compiler's willingness to resolve overloads of
Foo.Add()
in the syntactic-sugar initializer usage makes sense when you look at it that way. I think it's great to be able to do that, but I'm not 100% comfortable with the syntax they chose. If you found the assignment operator to be a red herring, others will too.But I'm not the Syntax Arbiter, and that's probably best for all concerned.
Finally, this also works with object initializers:
So...
Which actually turns into...
We can't initialize
Bar.Baz
because it's got no setter, but we can initialize its properties just as we can initialize the items inFoo
. And that's true even if they've already been initialized by a different object initializer attached to the actual constructor.Collection initializers, as you'd expect, are cumulative:
Bar.Foo
will have three items:{ "1000", "0", "Beringia" }
.When you think of the curly braces as shorthand for a column of assignment statements or
Add()
overload calls, it all snaps into focus.But I agree that the equals sign is jarring in cases where the lvalue is not actually being assigned to.
Bonus
Here's another pattern matching feature I learned about from that Eric Lippert article:
Therefore...
All you need for
select
to work is that the thing you're selecting from has to have a method namedSelect
that returns something that quacks likeIEnumerable
as outlined in @EricLippert's remarks onforeach
in the linked article (thanks Eric!), and takes aFunc<T,T>
parameter.Not always. What you're thinking of is:
That's the array initializer. However, you also have:
This is an object initializer. What you have in your "replacement" broken code is another thing entirely - the collection initializer. This works with any type that implements
IEnumerable
and has anAdd
method with the proper arguments (in this case, something likepublic void Add(View view)
).In your case, the latter two are used, and the collection initializer doesn't instantiate a new collection. A simple sample code:
In this case, the code in
Main
translates roughly to this equivalent C# code:This form is only valid inside an object initializer, though - it's designed specifically for the case where you have a
readonly
field that you need to initialize using the object/collection initializer syntax. For example, this doesn't work:If the compiler used the same "trick" as before, it could only add items to the collection - but it has no guarantee that the collection is empty (though even in the object initializer case, this is only a "guarantee by convention" - if you initialize the list with some items first, they will remain when the "assignment" happens).
That's all, folks :)
There seems to be some confusion in the other two answers as to how this actually works. I refer you to the C# specification section on object initializers, which clearly summarizes the semantics:
Remember, the specification is published for your convenience; if you have a question about the meaning of a C# language construct, it (or the printed annotated version "The C# Programming Language") should be your first reference.