[英]Does the null-conditional operator function the same with delegates and regular objects?
我目前正在處理一些線程敏感的代碼。
在我的代碼中,我有一個由兩個不同線程操縱的對象列表。 一個線程可以向此列表添加對象,而另一個線程可以將其設置為null。
在上面的參考文獻中,它特別提到代表們:
myDelegate?.Invoke()
相當於:
var handler = myDelegate;
if (handler != null)
{
handler(…);
}
我的問題是,這個行為是否相同,例如List<>
? 例如:
方法是:
var myList = new List<object>();
myList?.Add(new object());
保證相當於:
var myList = new List<object>();
var tempList = myList;
if (tempList != null)
{
tempList.Add(new object());
}
?
編輯:
請注意(委托如何工作)之間存在差異:
var myList = new List<int>();
var tempList = myList;
if (tempList != null)
{
myList = null; // another thread sets myList to null here
tempList.Add(1); // doesn't crash
}
和
var myList = new List<int>();
if (myList != null)
{
myList = null; // another thread sets myList to null here
myList.Add(1); // crashes
}
這是一個微妙的問題,需要仔細分析。
首先,問題中提出的代碼是沒有意義的,因為它對保證不為null的局部變量進行空檢查。 據推測,真實代碼從非局部變量讀取,該變量可能為空,也可能不為空,並且可以在多個線程上進行更改。
這是一個非常危險的位置,我強烈反對你不要追求這個架構決定 。 找到另一種在工人之間共享內存的方法。
要解決您的問題:
問題的第一個版本是:是?.
運算符與您引入臨時的版本具有相同的語義?
是的,它確實。 但我們還沒有完成。
你沒有問的第二個問題是:C#編譯器,抖動或CPU是否可能導致帶有臨時版本的版本引入額外的讀取? 也就是說,我們是否有保證
var tempList = someListThatCouldBeNull;
if (tempList != null)
tempList.Add(new object());
永遠不會像你寫的那樣被執行
var tempList = someListThatCouldBeNull;
if (tempList != null)
someListThatCouldBeNull.Add(new object());
“引入讀取”的問題在C#中很復雜,但簡短版本是:一般來說,您可以假設讀取不會以這種方式引入。
我們好嗎? 當然不是。 代碼完全不是線程安全的,因為可能會在多個線程上調用Add
,這是未定義的行為!
假設我們以某種方式解決了這個問題。 現在好事嗎?
不,我們仍然不應該對此代碼有信心。
為什么不?
原始海報沒有顯示任何保證正在讀取someListThatCouldBeNull
的最新值的someListThatCouldBeNull
。 它是在鎖定下訪問的嗎? 它不穩定嗎? 是否引入了記憶障礙? C#規范非常明確,如果沒有涉及鎖或揮發物的特殊效果,讀數可能會被任意向后移動。 您可能正在讀取緩存的值。
同樣,我們還沒有看到執行寫操作的代碼; 這些寫作可以任意移動到將來。 移動到過去的讀取或移動到將來的寫入的任何組合都可能導致讀取“陳舊”值。
現在假設我們解決了這個問題。 這會解決整個問題嗎? 當然不是。 我們不知道涉及多少線程,或者這些線程中是否有任何線程也在讀取相關變量,以及這些讀取是否存在任何假設的排序約束 。 C# 不要求有秩序的全球一致的視圖的所有讀取和寫入! 兩個線程可能不同意對volatile變量進行讀寫操作的順序。 也就是說,如果內存模型允許兩個可能的觀察順序,則一個線程觀察一個線程是合法的,而另一個線程觀察另一個線程是合法的。 如果您的程序邏輯隱含地依賴於單個觀察到的讀寫順序,則您的程序是錯誤的 。
現在也許你明白為什么我強烈建議不要以這種方式分享內存。 這是一個微妙的錯誤的雷區。
那你該怎么辦?
答案是肯定的。
var myList = new List<object>();
myList?.Add(new object());
編譯如下( 如此處所示 )
List<object> list = new List<object>();
if (list != null)
{
list.Add(new object());
}
在這個答案中 ,Eric Lippert確認在所有情況下都使用臨時變量,這將阻止“?”。 運算符導致NullReferenceException或訪問兩個不同的對象。 但是,有許多其他因素可以使這些代碼不是線程安全的,請參閱Eric的回答 。
UPD:解決未創建臨時變量的聲明:不需要為局部變量引入臨時變量。 但是,如果您嘗試訪問可能被修改的內容,則會創建一個變量。 使用相同的SharpLab和略微修改的代碼,我們得到:
using System;
using System.Collections.Generic;
public class C {
public List<Object> mList;
public void M() {
this.mList?.Add(new object());
}
}
變
public class C
{
public List<object> mList;
public void M()
{
List<object> list = mList;
if (list != null)
{
list.Add(new object());
}
}
}
是的,他們是一樣的。 您還可以在下面看到由Ildasm生成的基礎IL:
public void M()
{
var myList = new List<object>();
myList?.Add(new object());
}
這將是:
.method public hidebysig instance void M() cil managed
{
// Code size 25 (0x19)
.maxstack 2
.locals init (class [System.Collections]System.Collections.Generic.List`1<object> V_0)
IL_0000: nop
IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<object>::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brtrue.s IL_000c
IL_000a: br.s IL_0018
IL_000c: ldloc.0
IL_000d: newobj instance void [System.Runtime]System.Object::.ctor()
IL_0012: call instance void class [System.Collections]System.Collections.Generic.List`1<object>::Add(!0)
IL_0017: nop
IL_0018: ret
} // end of method C::M
和:
public void M2()
{
List<object> list = new List<object>();
if (list != null)
{
list.Add(new object());
}
}
這將是:
.method public hidebysig instance void M2() cil managed
{
// Code size 30 (0x1e)
.maxstack 2
.locals init (class [System.Collections]System.Collections.Generic.List`1<object> V_0,
bool V_1)
IL_0000: nop
IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<object>::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldnull
IL_0009: cgt.un
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: brfalse.s IL_001d
IL_000f: nop
IL_0010: ldloc.0
IL_0011: newobj instance void [System.Runtime]System.Object::.ctor()
IL_0016: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<object>::Add(!0)
IL_001b: nop
IL_001c: nop
IL_001d: ret
} // end of method C::M2
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.