2016-09-01 2 views
12

Ich habe dieses Codebeispiel gesehen und es sieht so aus, als ob es einer Liste einen Array-Initialisierer zuweist. Ich dachte, es würde nicht funktionieren, aber irgendwie kompiliert es. Ist {} kein Array-Initialisierer? Kinder sind vom Typ IList. Wie funktioniert es ohne die "neue Liste" vor den geschweiften Klammern?Wie funktioniert diese Listenzuordnung?

 var nameLayout = new StackLayout() 
     { 
      HorizontalOptions = LayoutOptions.StartAndExpand, 
      Orientation = StackOrientation.Vertical, 
      Children = { nameLabel, twitterLabel } 
     }; 

Edit: Wenn ich Children = new List<View>{ nameLabel, twitterLabel } versucht, gibt der Compiler diese Warnung: „Eigentum oder Indexer Layout.Children kann nicht zugewiesen werden, ist es schreibgeschützt.“

Das Codefragment ist von Xamarin durch die Art und Weise: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/

+0

Ich habe das gerade neulich entdeckt, ich konnte aber auch noch nicht genau feststellen, wie es funktioniert. – DLeh

+1

Entsprechend der [MSDN-Dokumentation zu Initialisierer] (https://msdn.microsoft.com/en-us/library/bb384062.aspx) scheint es, dass die Kompilierung 'Add' wiederholt aufruft, wenn dieser Initialisierer verwendet wird. Beachten Sie, dass Sie diese Syntax nicht direkt in einer Variablendeklaration verwenden können. z.B. 'IList children = {" childfoo "," childbar "}' kompiliert nicht –

+0

@ stephen.vakil: Manchmal können Sie irgendwie fast. 'string [] children = {" childfoo "," childbar "};' – recursive

Antwort

10

Das ist ein Spezialfall eines Auflistungsinitialisierer ist.

In C# wurden die geschweiften Klammern des Array-Initialisierers verallgemeinert, um mit jedem Auflistungsklassenkonstruktor zu arbeiten.

Jede Klasse unterstützt diese, wenn sie System.Collections.IEnumerable implementiert und eine oder mehrere Methoden Add() hat. Eric Lippert has a good post about this type of "pattern matching" in C#: Was der Compiler hier macht, ist, was sie "duck typing" nennen, anstatt konventioneller stark typisierter OOP, wo die Fähigkeiten einer Klasse basierend auf Vererbung und Schnittstellenimplementierung erkannt werden. C# macht das an einigen Stellen. Es gibt eine Menge Sachen in diesem Artikel, die ich nicht kannte.

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}"); 
    } 
} 

Und dann diese kompiliert:

var f = new Foo { 0, 1, 2, "Zanzibar", { DateTime.Now, 3.7 } }; 

, die für diese syntaktische Zucker ist:

var f = new Foo(); 

f.Add(0); 
f.Add(1); 
f.Add(2) 
f.Add("Zanzibar"); 
f.Add(DateTime.Now, 3.7); 

Sie ein paar ziemlich seltsame Spiele mit diesen spielen. Ich weiß nicht, ob es eine gute Idee ist, alles heraus zu gehen (eigentlich weiß ich - es ist nicht), aber Sie können. Ich habe eine Befehlszeilenparser-Klasse geschrieben, in der Sie die Optionen über einen Auflistungsinitialisierer definieren können. Es hat über ein Dutzend Überladungen von Add mit variierenden Parameterlisten, von denen viele generisch sind. Alles was der Compiler ableiten kann, ist faires Spiel.

Auch hier können Sie diese Push über kehrt zum Punkt der Funktion Missbrauch zu verringern.

Was Sie sehen, ist eine Erweiterung der gleichen initializer Syntax, wo es kann Sie eine Auflistungsinitialisierer für ein nicht übertragbares Element zu tun, die die Klasse selbst bereits erstellt:

public class Bar 
{ 
    public Foo Foo { get; } = new Foo(); 
} 

Und jetzt .. .

var b = new Bar { Foo = { 0, "Beringia" } }; 

{ 0, "Beringia" } ist eine Sammlung Initialisierer für die Foo weise, dass für Bar selbst erzeugt; es ist syntaktischer Zucker für diesen:

var b = new Bar(); 

b.Foo.Add(0); 
b.Foo.Add("Beringia"); 

Die Bereitschaft der Compiler Überlastungen von Foo.Add() in der syntaktisch-Zucker initializer Nutzung sinnvoll ist, zu lösen, wenn man es so aussehen. Ich denke, es ist großartig, das zu können, aber ich bin mit der von ihnen gewählten Syntax nicht zu 100% zufrieden. Wenn Sie den Zuweisungsoperator als Ablenkungsmanöver gefunden haben, werden es auch andere tun.

Aber ich bin nicht der Syntax Arbiter, und das ist wahrscheinlich am besten für alle Beteiligten.

Schließlich funktioniert das auch mit Objektinitialisierer:

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" } }; 

, die in tatsächlich dreht ...

var b = new Bar(); 

b.Foo.Add(0); 
b.Foo.Add("Beringia"); 
b.Baz.Name = "Arbitrary"; 

Wir können nicht initialisiert werden Bar.Baz weil es keinen Setter hat, aber wir können seine Eigenschaften initialisieren, genauso wie wir es initialisieren können ems in Foo. Und das ist auch dann der Fall, wenn sie bereits von einem anderen Objektinitialisierer initialisiert wurden, der an den eigentlichen Konstruktor angehängt wurde.

Auflistungsinitialisierer sind wie erwartet kumulativ: Bar.Foo wird drei Elemente haben: { "1000", "0", "Beringia" }.

Wenn Sie an die geschweiften Klammern als Kurzschreibweise für eine Spalte von Zuweisungsanweisungen oder Add() Überladungsaufrufe denken, wird alles in den Fokus gerückt.

Aber ich stimme zu, dass das Gleichheitszeichen in Fällen, in denen der Lvalue nicht tatsächlich zugewiesen wird, schreit.

Bonus

Hier ist eine andere Pattern-Matching-Funktion, die ich gelernt, über from that Eric Lippert article:

public static class HoldMyBeerAndWatchThis 
{ 
    public static IEnumerable<int> Select(Func<String, String> f) 
    { 
     yield return f("foo").Length; 
    } 
} 

Deshalb ...

var x = from s in HoldMyBeerAndWatchThis select s; 

Alles, was Sie für select arbeiten müssen, ist, dass das, was Sie‘ Die Auswahl von muss eine Methode namens Select haben, die etwas zurückgibt, das quatscht wieIEnumerable wie in @ EricLippert Bemerkungen auf foreach in the linked article (danke Eric!), Und nimmt eine Func<T,T> Parameter.

+8

Der 'Children'-Teil ist jedoch ein Sammlungsinitialisierer - ich denke, das ist es, worum der OP eigentlich fragt. –

+0

@JonSkeet Ohhhh, duh, richtig. Hektisch Aktualisierung –

+0

Dokumentation Referenz: http://stackoverflow.com/documentation/c%23/21/collection-initialisers/7975/using-collection-initializer-inside-object-initializer#t=201609011535273502169 – DLeh

3

Nicht immer. Was Sie denken ist:

int[] array = new int[] { 1, 2, 3, 4 }; 

Das ist der Array-Initialisierer. Sie haben jedoch auch:

SomeObject obj = new SomeObject { Name = "Hi!", Text = "Some text!" }; 

Dies ist ein Objektinitialisierer. Was Sie in Ihrem "Ersatz" -Bruchcode haben, ist eine ganz andere Sache - der Sammlungsinitialisierer. Dies funktioniert mit jedem Typ, der IEnumerable implementiert und hat eine Add Methode mit den richtigen Argumenten (in diesem Fall so etwas wie public void Add(View view)).

SomeList list = new SomeList { "Hi!", "There!" }; 

In Ihrem Fall sind die beiden letzteren verwendet, und die Auflistungsinitialisierer eine neue Kollektion nicht instanziiert ist.Ein einfacher Beispielcode:

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 diesem Fall übersetzt der Code in Main grob auf diesen äquivalent C# -Code:

var some = new SomeObject(); 
some.List.Add("Hi!"); 
some.List.Add("There!"); 

Diese Form ist innerhalb eines Objektinitialisierer nur gültig, wenn - es ist speziell Für den Fall, dass Sie ein readonly-Feld haben, das Sie initialisieren müssen, indem Sie die Initialisierungssyntax des Objekts/der Sammlung verwenden. Zum Beispiel funktioniert das nicht:

var some = new SomeObject(); 
some.List = { "Hi!", "There!" }; 

Wenn der Compiler den gleichen „Trick“ wie bisher verwendet, könnte es nur Elemente zur Sammlung - aber keine Garantie hat, dass die Sammlung leer ist (obwohl im Fall des Objektinitialisierers ist dies nur eine "Garantie nach Konvention" - wenn Sie die Liste zuerst mit einigen Elementen initialisieren, bleiben sie bei der "Zuweisung" erhalten.

Das ist alles, Leute :)

+0

So weit ich verstehe, ist dies eine spezielle Regel, die nur im Falle der Sammlung Initialisierung innerhalb Objektinitialisierung gilt. Gibt es dazu offizielle Dokumente? –

+0

@JohnL. Soweit ich weiß, hatte die letzte C# ECMA-Spezifikation noch keine Objektinitialisierer. Ich denke nicht, dass es eine offizielle Dokumentation darüber gibt. – Luaan

+1

Natürlich gibt es offizielle Dokumentation darüber; Diese Funktion wurde vor über zehn Jahren * implementiert. Siehe jede veröffentlichte C# -Spezifikation oder MSDN oder die Dead-Bäume "The C# Programming Language" aus den letzten zehn Jahren. –

5

Es scheint eine gewisse Verwirrung in den beiden anderen Antworten zu sein, wie dies tatsächlich funktioniert. Ich verweise Sie auf die C# Spezifikation Abschnitt auf Objektinitialisierer, die eindeutig die Semantik fasst zusammen:

Ein Elementinitialisierung, die eine Auflistungsinitialisierer gibt an, nach dem Gleichheitszeichen ist eine Initialisierung eines eingebetteten Sammlung. Anstatt dem Feld oder der Eigenschaft eine neue Auflistung zuzuweisen, werden die im Initialisierer angegebenen Elemente zur Auflistung hinzugefügt, auf die das Feld oder die Eigenschaft verweist.

Denken Sie daran, die Spezifikation wird für Ihre Bequemlichkeit veröffentlicht; Wenn Sie eine Frage zur Bedeutung eines C# -Sprachkonstrukts haben, sollte es (oder die gedruckte kommentierte Version "Die C# -Programmiersprache") Ihre erste Referenz sein.