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 more Add()
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.
public class Foo : List<String>
{
public void Add(int n)
{
base.Add(n.ToString());
}
public void Add(DateTime dt, double x)
{
base.Add($"{dt.ToShortDateString()} {x}");
}
}
And then this compiles:
var f = new Foo { 0, 1, 2, "Zanzibar", { DateTime.Now, 3.7 } };
That's syntactic sugar for this:
var f = new Foo();
f.Add(0);
f.Add(1);
f.Add(2)
f.Add("Zanzibar");
f.Add(DateTime.Now, 3.7);
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:
public class Bar
{
public Foo Foo { get; } = new Foo();
}
And now...
var b = new Bar { Foo = { 0, "Beringia" } };
{ 0, "Beringia" }
is a collection initializer for the Foo
instance that Bar
created for itself; it's syntactic sugar for this:
var b = new Bar();
b.Foo.Add(0);
b.Foo.Add("Beringia");
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:
public class Baz
{
public String Name { get; set; }
}
public class Bar
{
public Foo Foo { get; } = new Foo { 1000 };
public Baz Baz { get; } = new Baz { Name = "Initial name" };
}
So...
var b = new Bar { Foo = { 0, "Beringia" }, Baz = { Name = "Arbitrary" } };
Which actually turns into...
var b = new Bar();
b.Foo.Add(0);
b.Foo.Add("Beringia");
b.Baz.Name = "Arbitrary";
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 in Foo
. 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:
public static class HoldMyBeerAndWatchThis
{
public static IEnumerable<int> Select(Func<String, String> f)
{
yield return f("foo").Length;
}
}
Therefore...
var x = from s in HoldMyBeerAndWatchThis select s;
All you need for select
to work is that the thing you're selecting from has to have a method named Select
that returns something that quacks like IEnumerable
as outlined in @EricLippert's remarks on foreach
in the linked article (thanks Eric!), and takes a Func<T,T>
parameter.
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:
A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the field or property, the elements given in the initializer are added to the collection referenced by the field or property.
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.
Not always. What you're thinking of is:
int[] array = new int[] { 1, 2, 3, 4 };
That's the array initializer. However, you also have:
SomeObject obj = new SomeObject { Name = "Hi!", Text = "Some text!" };
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 an Add
method with the proper arguments (in this case, something like public void Add(View view)
).
SomeList list = new SomeList { "Hi!", "There!" };
In your case, the latter two are used, and the collection initializer doesn't instantiate a new collection. A simple sample code:
void Main()
{
var some = new SomeObject { List = { "Hi!", "There!" } };
some.List.Dump();
}
public class SomeObject
{
public List<string> List { get; private set; }
public SomeObject()
{
List = new List<string>();
}
}
In this case, the code in Main
translates roughly to this equivalent C# code:
var some = new SomeObject();
some.List.Add("Hi!");
some.List.Add("There!");
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:
var some = new SomeObject();
some.List = { "Hi!", "There!" };
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 :)