2016-09-01 60 views
12

我已經看過這個代碼示例,它看起來像它將一個數組初始值設定項分配給List。我認爲這是行不通的,但不知何故它編譯。 {}不是數組初始值設定項嗎?孩子是IList類型的。如果沒有大括號之前的「新列表」,它如何工作?此列表分配如何工作?

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

編輯:當我試圖Children = new List<View>{ nameLabel, twitterLabel },編譯器爲這樣的警告:「屬性或索引Layout.Children不能被分配到,它是隻讀的。」

的代碼片段來自Xamarin的方式:https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/

+0

我剛剛在前幾天發現了這一點,我還沒有能夠確切地分離它是如何工作的。 – DLeh

+1

根據[初始化程序中的MSDN文檔](https://msdn.microsoft.com/en-us/library/bb384062.aspx),似乎編譯器在使用此初始化程序時會重複調用「Add」。請注意,您不能在變量聲明中直接使用此語法。例如'IList children = {「childfoo」,「childbar」}'不會編譯 –

+0

@ stephen.vakil:有時候,你幾乎可以。 'string [] children = {「childfoo」,「childbar」};' – recursive

回答

10

這是一個集合初始化器的一個特例。

在C#中,數組初始化符花括號已被推廣到與任何集合類構造函數一起工作。

任何類支持這些如果它實現System.Collections.IEnumerable並且有一個或多個Add()方法。 Eric Lippert has a good post about this type of "pattern matching" in C#:編譯器在這裏做的是他們稱之爲「duck typing」的東西,而不是傳統的強類型OOP,其中類的功能基於繼承和接口實現進行識別。 C#在幾個地方執行此操作。那篇我不知道的文章裏有很多東西。

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

然後這個編譯:

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

這句法糖爲此:

var f = new Foo(); 

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

你可以玩這些一些很奇怪的比賽。我不知道全力以赴是否是個好主意(其實我知道 - 事實並非如此),但你可以。我寫了一個命令行解析器類,您可以在其中通過集合初始值設定器定義選項。它有12個過載的Add,帶有不同的參數列表,其中很多都是通用的。編譯器可以推斷的任何東西都是公平的遊戲。

再一次,您可以將這種超越遞減的功能推向功能濫用的地步。

你看到的是相同的初始化語法,它可以讓你的這個類本身就已經創造了一個不可轉讓的成員做集合初始化的擴展:

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

現在.. 。

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

{ 0, "Beringia" }對於Foo實例Bar爲自己創造了一個集合初始化;它的語法糖這樣的:

var b = new Bar(); 

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

編譯器的解決語法糖初始化使用的Foo.Add()重載意願是有道理的,當你看它的方式。我認爲能夠做到這一點很棒,但我對他們選擇的語法並不滿意。如果你發現賦值運算符是一個紅鯡魚,其他人也會。

但我不是語法仲裁者,這可能是最好的所有有關。

最後,這還與對象初始化:

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

所以......

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

裏面居然變成...

var b = new Bar(); 

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

我們不能初始化Bar.Baz,因爲它沒有setter,但我們可以初始化它的屬性,就像我們可以初始化它的屬性一樣ems在Foo。即使它們已經被連接到實際構造函數的不同對象初始化器初始化,情況也是如此。

正如您所期望的,收集初始值設定項是累積值:Bar.Foo將包含三項:{ "1000", "0", "Beringia" }

當您將花括號視爲一列賦值語句或Add()過載調用的縮寫時,它將全部對焦。

但我同意,在左值沒有被分配到的情況下,等號是震耳欲聾的。

獎金

這裏的另一種模式匹配功能,我學到了from that Eric Lippert article

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

因此...

var x = from s in HoldMyBeerAndWatchThis select s; 

所有你需要的select工作就是你的事」重新選擇必須有一個名爲Select的方法,該方法返回那樣的庸醫像IEnumerable @ @ EricLippert關於foreach的評論在the linked article(感謝Eric!)中列出,並且需要Func<T,T>參數。

+8

'Children'部分雖然是一個集合初始值設定項,但我認爲這就是OP實際上詢問的內容。 –

+0

@JonSkeet Ohhhh,對,對。瘋狂更新 –

+0

文檔參考:http://stackoverflow.com/documentation/c%23/21/collection-initializers/7975/using-collection-initializer-inside-object-initializer#t=201609011535273502169 – DLeh

3

並非總是如此。你在想什麼是:

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

這是數組初始值設定項。但是,您也有:

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

這是一個對象初始值設定項。你在「替換」破解代碼中有什麼是完全不同的 - 集合初始值設定器。這適用於實施IEnumerable的任何類型,並且具有帶適當參數的Add方法(在這種情況下,類似於public void Add(View view))。

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

在你的情況,後兩者被使用,並且集合初始化不實例化一個新的集合。一個簡單的示例代碼:

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

在這種情況下,在Main代碼大致翻譯該等效C#代碼:

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

這種形式僅是一個對象初始化內部有效,雖然 - 它專門設計對於您需要使用對象/集合初始值設定項語法初始化的readonly字段的情況。例如,這不起作用:

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

如果編譯器使用相同的「絕招」和以前一樣,它只能項目添加到集合 - 但它不能保證該集合爲空(但即使在對象初始化器的情況下,這只是一個「按約定保證」 - 如果你首先用一些項目初始化列表,當「賦值」發生時它們將保持不變)。

這一切,鄉親:)

+0

所以據我所知,這是一個特殊的規則,只適用於對象初始化中的集合初始化情況。有沒有關於這方面的官方文件? –

+0

@JohnL。據我所知,最後一個C#ECMA規範還沒有對象初始化器。我不認爲有這方面的官方文件。 – Luaan

+1

當然有這方面的官方文檔;此功能在十年前已*實施*。查看任何已發佈的C#規範,或MSDN,或過去十年中的枯樹「The C#Programming Language」。 –

5

似乎有在其他兩個答案有些混亂,這實際上是如何工作的。關於對象初始值設定項的C#規範部分,清楚地概括了語義:

成員初始值設定項指定等號後面的集合初始值設定項是嵌入式集合的初始化。初始化程序中給出的元素將添加到由字段或屬性引用的集合中,而不是將新集合分配給字段或屬性。

請記住,規範的發佈是爲了您的方便;如果您對C#語言結構的含義有疑問,那麼它(或打印的註釋版本「C#編程語言」)應該是您的第一個參考。