簡體   English   中英

在foreach循環中更改另一個結構內的結構

[英]Changing a struct inside another struct in a foreach loop

打印以下代碼(調用MyMethod時):

0
0
0
1

我希望它能打印出來:

0
0
1
1

為什么是這樣?

碼:

private struct MyStruct
{
    public MyInnerStruct innerStruct;
}

private struct MyInnerStruct
{
    public int counter;

    public void AddOne()
    {
        ++counter;
    }
}

public static void MyMethod()
{
    MyStruct[] myStructs = new MyStruct[] { new MyStruct() };

    foreach (var myStruct in myStructs)
    {
        MyStruct myStructCopy = myStruct;

        Console.WriteLine(myStruct.innerStruct.counter);
        Console.WriteLine(myStructCopy.innerStruct.counter);

        myStruct.innerStruct.AddOne();
        myStructCopy.innerStruct.AddOne();

        Console.WriteLine(myStruct.innerStruct.counter);
        Console.WriteLine(myStructCopy.innerStruct.counter);
    }
}

您看到此行為的原因必須是使用迭代變量。 迭代變量是只讀的,因為在C#中你無法修改它們(C#lang spec section 8.8.4詳細說明了這一點

迭代變量對應於只讀局部變量,其范圍擴展到嵌入語句

使用只讀的可變結構是一種意外行為的途徑。 您不是直接使用變量,而是實際使用變量的副本。 因此,在myStruct的情況下,副本會增加,而不是實際值。 這就是原始值保持不變的原因。

Eric在這個主題上做了一篇相當深入的文章,你可以在這里訪問

你應該總是擁有不可變結構的另一個原因。

迭代變量是只讀的,通常禁止直接修改只讀結構,或者通過ref這樣的結構或其任何部分來修改任何會修改它的代碼。 不幸的是,有一個問題:盡管許多結構方法和屬性不會改變底層結構,但是在結構上調用方法或屬性需要通過ref傳遞它,無論方法或屬性是否會改變結構實例。 這給C#的設計者留下了五個選擇:

  1. 不允許在只讀上下文中的結構上使用struct方法或屬性,而不將結構顯式復制到可變存儲位置(很可能是局部變量)。
  2. 允許在只讀上下文中的結構上使用struct方法或屬性,並希望它們不會修改它們不應該的任何內容。
  3. 在只讀結構上調用方法或屬性時,靜默地創建結構的副本並將該副本提供給方法或屬性。
  4. 提供一種方法,通過該方法,結構方法或屬性可以聲明性地指示它們是否會改變底層結構; 只允許struct方法和屬性修改它們自己的字段(如果它們被聲明為這樣做),並且只允許將只讀結構傳遞給未聲明為修改它們的方法。
  5. 提供一種方法,通過該方法,結構方法或屬性可以聲明性地指示它們是否會改變底層結構; 禁止在只讀上下文中對結構使用此類方法和屬性,但在調用沒有此類聲明的方法時制作只讀結構的防御副本,以防方法應該具有但不具備這些方法和屬性。

選擇#1會有點惱人,特別是關於通過屬性暴露其狀態的結構。 從性能的角度來看,選擇#2或#4會很好,但是意外寫this結構例程可能會導致意外行為。 微軟選擇了#3(除了添加可選屬性外,選擇#5將是相同的)。 在許多方面,這是最糟糕的選擇(不寫this會比沒有復制操作的方法運行得慢,編寫this方法無論是否有效都無法正常工作,並且結構中唯一被破壞的方法寫一個只讀變量將是一個代碼已被破壞的結構的實例。 盡管如此,這種選擇現在已經很成熟,而且它就是這樣。

順便提一下,即使是嵌入在結構類型的只讀結構或屬性中的讀取操作結構也可能比使用非只讀字段要昂貴得多,而只是避免編寫它們。 在調用myStruct.innerStruct.AddOne() ,編譯器首先制作MyStruct的副本,然后復制myStruct.InnerStruct ,然后在其上調用AddOne()方法。 相比之下,當調用myStructCopy.innerStruct.AddOne() ,它可以通過引用AddOne()方法傳遞innerStruct而不復制任何東西。

我想你會發現MyStruct.innerStruct實際上返回了struct的副本,而不是struct本身。 因此,您正在增加副本的價值......

當然我最近在某處讀了一篇關於這個的博客。

如果您更改以下內容

myStruct.innerStruct.AddOne();
Console.WriteLine(myStruct.innerStruct.counter);

MyInnerStruct inner = myStruct.innerStruct;
inner.AddOne();
Console.WriteLine(inner.counter);

然后你應該看到它開始工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM