本文章是由機器翻譯。
C#
C# 6.0 的新功能與改良功能
雖然 C# 6.0 還不完整的它是點現在在這裡的特點是接近定稿。自 2014 年 5 月第條,"A C# 6.0 語言預覽"就一直有大量的改動和改進到 C# 6.0 在 CTP3 版本中的下一個版本的Visual Studio,代號為"14、"(msdn.microsoft.com/magazine/dn683793.aspx)。
在這篇文章,我會引入新的功能,並提供更新早于 5 月討論的功能。我也會保持一個全面的最新博客描述每個 C# 6.0 功能的更新。查閱在 itl.tc/csharp6。很多這些例子都是從我的書下, 一版"基本的 C# 6.0" (艾迪生-Wesley 專業)。
Null 條件運算子
即使是用最新的.NET 開發人員都可能熟悉舉出。這是一個例外,幾乎總是表明一個 bug,因為開發人員不能執行之前調用的成員 (空值) 的物件上足夠 null 檢查。請考量以下範例:
public static string Truncate(string value, int length)
{
string result = value;
if (value != null) // Skip empty string check for elucidation
{
result = value.Substring(0, Math.Min(value.Length, length));
}
return result;
}
如果不為 null 的檢查,該方法會引發舉出。雖然它很簡單,不必檢查 null 的字串參數是雖然有點囉嗦。通常情況下,那冗長的方法很可能是不必要考慮進行對比的頻率。C# 6.0 包括一個新的空條件運算子,它可説明您更簡潔地編寫這些檢查:
public static string Truncate(string value, int length)
{
return value?.Substring(0, Math.Min(value.Length, length));
}
[TestMethod]
public void Truncate_WithNull_ReturnsNull()
{
Assert.AreEqual<string>(null, Truncate(null, 42));
}
如 Truncate_WithNull_ReturnsNull 方法所示,如果事實上物件的值為 null,空條件運算子將返回 null。這引出了一個 null 條件運算子出現在調用鏈,在下一個示例內發生了什麼的問題:
public static string AdjustWidth(string value, int length)
{
return value?.Substring(0, Math.Min(value.Length, length)).PadRight(length);
}
[TestMethod]
public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended()
{
Assert.AreEqual<int>(42, AdjustWidth("Inigo Montoya", 42).Length);
}
即使子字串稱為通過 null 條件運算子和 null 值嗎?。子串看似能夠返回 null,語言行為你會想要什麼。它短路 PadRight,對的調用並立即返回 null,避免否則會舉出的程式設計錯誤。這是一個概念,稱為空傳播。
Null 條件運算子調用的目標方法和任何其他的方法內調用鏈之前有條件地檢查 null。有可能,這可以產生一個驚人的結果,如在語句文本嗎。Length.GetType。
如果調用目標為 null 時,返回 null null 條件,由此產生的資料是什麼類型調用一個傳回值類型的成員 — — 給定的數值型別不能為空?例如,從值返回的資料類型嗎。長度不能單純是 int。答案當然是可以為 null 的 (int)?。事實上,試圖把結果賦值給 int 只是將產生一個編譯錯誤:
int length = text?.Length; // Compile Error: Cannot implicitly convert type 'int?' to 'int'
Null 條件有兩種語法形式。第一,是前點運算子的問號 (嗎?。)。第二個是索引運算子結合使用問號。例如,給出了一個集合,而不是索引到集合之前顯式檢查 null 你可以使用 null 條件運算子:
public static IEnumerable<T> GetValueTypeItems<T>(
IList<T> collection, params int[] indexes)
where T : struct
{
foreach (int index in indexes)
{
T? item = collection?[index];
if (item != null) yield return (T)item;
}
}
此示例使用 null 條件指數形式的操作員嗎?[......],造成編入集合只,如果集合不為 null,則出現的索引。用這種形式的 null 條件運算子,T 嗎? 專案 = 集合嗎?[索引] 語句在行為上等效于:
T? item = (collection != null) ? collection[index] : null.
請注意 null 條件運算子只能檢索專案。它不會工作分配的專案。這意味著什麼,給定一個空的集合,不管怎麼說?
使用時,請注意隱式的含糊不清?[…]對參考型別。因為參考型別可以為 null,空導致從嗎?[…]運算子是含糊的地方集合是否為空或元素本身,事實上,null。
一個特別有用的應用程式的 null 條件運算子解析 C# C# 1.0 以來一直存在的特質 — — 檢查 null 之前調用的委託。考慮中的 C# 2.0 代碼圖 1。
圖 1 在調用委派之前檢查 Null
class Theremostat
{
event EventHandler<float> OnTemperatureChanged;
private int _Temperature;
public int Temperature
{
get
{
return _Temperature;
}
set
{
// If there are any subscribers, then
// notify them of changes in temperature
EventHandler<float> localOnChanged =
OnTemperatureChanged;
if (localOnChanged != null)
{
_Temperature = value;
// Call subscribers
localOnChanged(this, value);
}
}
}
}
利用空條件運算子,整個設置的實現被減少到只需:
OnTemperatureChanged?.Invoke(this, value)
你現在需要的就是對由一個 null 條件運算子的首碼的 Invoke 的調用。您不再需要將委託實例分配到一個本地變數,是執行緒安全的或者甚至顯式檢查 null 值之前調用的委託。
C# 開發人員想知道,是否這會改進最後四個版本。它最後會發生什麼。這一功能將改變你調用委託的方式。
Null 條件運算子可能在哪裡盛行的另一種常見模式是與聯合運算子的組合。而不是在調用長度之前檢查 null 在 linesOfCode 上,您可以編寫專案計數演算法,如下所示:
List<string> linesOfCode = ParseSourceCodeFile("Program.cs");
return linesOfCode?.Count ?? 0;
在這種情況下,任何空的集合 (沒有專案) 和一個空的集合都正常化返回相同的計數。總之,null 條件運算子將:
- 如果運算元為 null,返回 null
- 短路額外調用調用鏈中的,如果運算元為 null
- 如果目標成員傳回值類型,則返回可空類型 (System.Nullable < T >)
- 支援委託調用的執行緒安全的方式
- 可作為這兩個成員運算子 (嗎?。) 和索引運算子 (? […])
自動屬性初始值設定項
任何.NET 開發人員永遠也得當 struct 無疑一直困擾需由多少語法使類型不可變,(如.NET 標準建議,它應該是)。問題在於,這一事實為唯讀的屬性應該具有:
- 讀只定義支援欄位
- 支援欄位從建構函式中初始化
- 顯式實現的屬性 (而不是使用自動屬性)
- 返回支援欄位顯式的 getter 執行
所有這一切都只是"正常"實現永恆不變的屬性。這種行為然後重複的類型的所有屬性。所以做正確的事情需要明顯更多的努力,比易碎的方法。C# 6.0 來到救援與一個稱為自動屬性初始值設定項 (CTP3 還包括支援初始化運算式) 的新功能。自動屬性初始值設定項允許直接在其聲明中的屬性賦值。對於唯讀屬性,它負責確保該屬性不能改變所需的所有儀式。例如,請考慮在此示例中的指紋類:
public class FingerPrint
{
public DateTime TimeStamp { get; } = DateTime.UtcNow;
public string User { get; } =
System.Security.Principal.WindowsPrincipal.Current.Identity.Name;
public string Process { get; } =
System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}
如代碼所示,屬性初始值設定項允許將屬性分配一個初始的值作為屬性聲明的一部分。該屬性可以是唯讀模式 (只吸氣劑) 或讀/寫 (setter 和 getter)。當它是唯讀的時底層的支援欄位自動是用唯讀修飾符聲明的。這將確保它是不可變後初始化。
初始值設定項可以是任何運算式。例如,通過利用條件運算子,您可以使用預設值初始化值:
public string Config { get; } = string.IsNullOrWhiteSpace(
string connectionString =
(string)Properties.Settings.Default.Context?["connectionString"])?
connectionString : "<none>";
在此示例中,請注意,聲明運算式使用 (見 itl.tc/?p=4040) 正如在上一篇文章中討論。如果你需要更多比一個運算式,您可以重構的初始化成一個靜態方法並調用該方法。
名稱的運算式
CTP3 版本中引入的另一項是支援名稱的運算式。有很的多次,當您需要使用"魔術字串"在您的代碼中。這種"魔幻字串"是正常 C# 字串映射到您的代碼中的程式元素。舉個例子,投擲 ArgumentNullException,會在相應的參數是不正確名稱使用該字串。不幸的是,這些神奇的字串有沒有編譯時驗證和任何程式元素更改 (如重新具名引數) 不會自動更新魔術的字串,造成不一致,一直沒抓到由編譯器。
在其他的場合,如當提高事件獲取一個值,您可以避免通過樹中提取名稱的運算式體操的神奇的字串。這是或許有點令人厭煩給出操作簡單,只是標識該程式元素的名稱。在這兩種情況下,解決方案是不甚理想。
為了解決這個特質,C# 6.0 提供訪問"的程式元素"的名稱,無論是類名、 方法名、 參數名稱或特定的屬性名稱 (也許當使用反射)。例如,在代碼圖 2 使用的名稱的運算式來提取的參數的名稱。
圖 2 中提取參數名稱與名稱表達
void ThrowArgumentNullExceptionUsingNameOf(string param1)
{
throw new ArgumentNullException(nameof(param1));
}
[TestMethod]
public void NameOf_UsingNameofExpressionInArgumentNullException()
{
try
{
ThrowArgumentNullExceptionUsingNameOf("data");
Assert.Fail("This code should not be reached");
}
catch (ArgumentNullException exception)
{
Assert.AreEqual<string>("param1", exception.ParamName);
}
作為測試的方法表明,ArgumentNullException 的參數名稱屬性具有值 param1 — — 使用 nameof(param1) 表達方法中設置的值。名稱表達並不只限于參數。您可以使用它來檢索任何的程式設計元素,如中所示圖 3。
圖 3 中檢索其他程式元素
namespace CSharp6.Tests
{
[TestClass]
public class NameofTests
{
[TestMethod]
public void Nameof_ExtractsName()
{
Assert.AreEqual<string>("NameofTests", nameof(NameofTests));
Assert.AreEqual<string>("TestMethodAttribute",
nameof(TestMethodAttribute));
Assert.AreEqual<string>("TestMethodAttribute",
nameof(
Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute));
Assert.AreEqual<string>("Nameof_ExtractsName",
string.Format("{0}", nameof(Nameof_ExtractsName)));
Assert.AreEqual<string>("Nameof_ExtractsName",
string.Format("{0}", nameof(
CSharp6.Tests.NameofTests.Nameof_ExtractsName)));
}
}
}
名稱運算式只檢索的最終的識別碼,即使您使用更明確的帶點的名稱。此外,對於屬性而言,"屬性"尾碼不被隱含。相反,它已編譯所需。它提供了很好的機會來清理亂碼。
主建構函式
自動屬性初始值設定項是與主建構函式的組合尤其是有用的。主建構函式使您可以減少儀式上共同的物件模式方式。自 5 月以來,此功能已得到顯著的改善。更新包括:
- 一個可選的實施機構的主建構函式:這允許主建構函式參數驗證和初始化,這以前不支援的事情。
- 取消欄位參數:宣言 》 通過主建構函式參數的欄位。(不前進與此功能作為它的定義是正確的決定,因為它再也不會強制特定的命名約定,以 C# 以前是矛盾的方式。
- 為表達支援健全的函數和屬性 (在本文後面討論)。
隨著 Web 服務、 多層應用程式、 資料服務、 Web API,JSON 和類似技術的普及,類的一個常見的形式是資料傳輸物件 (DTO)。DTO 通常不會有很多執行行為,但側重于資料存儲簡單。這種注重簡潔性使主建構函式,令人信服的。例如,請考慮永恆不變的對資料結構,此示例中所示:
struct Pair<T>(T first, T second)
{
public T First { get; } = first;
public T Second { get; } = second;
// Equality operator ...
}
建構函式的定義 — — 雙 (字串首先,字串第二) — — 合併到類聲明。此選項指定建構函式的參數是第一和第二次 (每個類型為 T 的)。這些參數都還在屬性初始值設定項中引用和分配到其對應的屬性。當你觀察簡單的這個類的定義,其支援不變性以及所需的建構函式 (所有的屬性欄位的初始值設定項) 時,你看看它如何説明您正確的代碼。這導致一種常見的模式,以前需要不必要的詳細程度明顯改善。
主建構函式機構指定上主建構函式的行為。這可以説明您實現等效的功能上主建構函式,你可以在建構函式中一般。例如下, 一步提高可靠性的雙 < T > 資料結構可能是提供屬性驗證。這種驗證可以確保 Pair.First 為 null 的值無效。CTP3 現在包括主建構函式的主體 — — 如果沒有聲明,如中所示的建構函式體圖 4。
圖 4 執行主建構函式體
struct Pair<T>(T first, T second)
{
{
if (first == null) throw new ArgumentNullException("first");
First = first; // NOTE: Not working in CTP3
}
public T First { get; }; // NOTE: Results in compile error for CTP3
public T Second { get; } = second;
public int CompareTo(T first, T second)
{
return first.CompareTo(First) + second.CompareTo(Second);
}
// Equality operator ...
}
為清楚起見,我將主建構函式的屍體放置在第一個類的成員。然而,這不是一個 C# 要求。主建構函式體可以出現在任何順序相對於其他的類成員。
雖然不在 CTP3 功能,唯讀屬性的另一個特點是你可以將它們直接從分配在建構函式中 (例如,第一次 = 第一次)。這並不只限于主要的建構函式,但可用於任何建構函式的成員。
支援自動屬性初始值設定項有趣的結果是它消除了許多在早期版本中發現的案件在這裡你需要顯式欄位聲明。明顯的例子,它不能消除是需要驗證的 setter 的地方的情況。另一方面,需要唯讀欄位的聲明變得幾乎不推薦使用。現在,每當聲明為唯讀的欄位,您可以聲明唯讀自動屬性可能為私有的如果這種水準的封裝是必需。
和方法具有參數,第一個和第二次 — — 看似重疊的主建構函式的參數名稱。因為主建構函式名稱範圍內的自動屬性初始值設定項,第一和第二可能看起來含糊不清。幸運的是,這不是這樣。範圍規則在你還沒看到在 C# 中前一個不同的維度上的樞紐分析表。
在 C# 6.0 中,範圍總是確定在代碼中的變數聲明位置。參數綁定內他們説明申報,域被綁定的方法內的類和如果內部聲明的變數聲明都受 if 條件身體的聲明。
與此相反的是,主建構函式參數都是受時間約束。主建構函式執行時,主建構函式參數僅"活著"。這一時限是顯而易見的主建構函式體。它也許是較為明顯的自動屬性初始值設定項案例。
然而,像欄位初始值設定項轉換為語句執行作為 C# 1.0 + 中的類初始化的一部分,自動屬性初始值設定項相同的方法實現。換句話說,主建構函式參數的範圍是綁定到類初始值設定項和主建構函式體的壽命。對主建構函式參數的自動屬性初始值設定項或主建構函式體之外的任何引用將導致編譯錯誤。
有幾個額外的概念來記住重要的主建構函式相關。只有主建構函式可以調用基建構函式。你這樣使用主建構函式聲明後面的基地 (上下文) 關鍵字:
class UsbConnectionException(
string message, Exception innerException, HidDeviceInfo hidDeviceInfo):
Exception (message, innerException)
{
public HidDeviceInfo HidDeviceInfo { get; } = hidDeviceInfo;
}
如果您指定附加建構函式,建構函式調用鏈必須最後調用主建構函式。這意味著主建構函式不能有這樣一個初始值設定項。所有其他建構函式必須具有他們,假設主建構函式不也預設的建構函式:
public class Patent(string title, string yearOfPublication)
{
public Patent(string title, string yearOfPublication,
IEnumerable<string> inventors)
...this(title, yearOfPublication)
{
Inventors.AddRange(inventors);
}
}
我希望這些例子能説明證明主建構函式帶簡約到 C#。他們更多的機會,簡單地說,做簡單的事情,而不是簡單的事情複雜的方式。它偶爾需要類具有多個建構函式和調用鏈,使代碼難以閱讀。如果你遇到一個在那裡的主建構函式語法使您的代碼看起來更複雜而非簡化 it 的場景,然後不要使用主建構函式。C# 6.0 中,所有的增強功能,如果你不喜歡的功能,或者如果它使代碼難以閱讀,就不要用它。
表達濃郁的函數和屬性
濃郁的運算式函數是在 C# 6.0 中的另一種語法簡化。這些都是沒有聲明身體的功能。相反,您實現它們與函式宣告之後的運算式。
例如,重寫 ToString 無法添加到雙 < T > 類:
public override string ToString() => string.Format("{0}, {1}", First, Second);
沒什麼特別是激進的是,酒體的表達功能。與的大多數功能在 C# 6.0 中,發現他們被打算作為執行簡單的情況下提供一種簡化的語法。當然,該運算式的返回類型必須標識在函式宣告的返回類型。在這種情況下,ToString 返回的字串,函數執行運算式一樣。返回 void 的方法或任務,應執行不返回任何內容的運算式要麼。
濃郁的運算式的化簡並不只限于功能。您還可以實現唯讀 (getter 只) 使用運算式的屬性 — — 表達體屬性。例如,您可以將文本成員添加到指紋類:
public string Text =>
string.Format("{0}: {1} - {2} ({3})", TimeStamp, Process, Config, User);
其他功能
有幾個特點不再計畫 C# 6.0:
- 索引的屬性運算子 ($) 已不再可用,並預計不會對 C# 6.0。
- 索引成員語法並不從事 CTP3,儘管它已預計在以後的版本的 C# 6.0 中返回:
var cppHelloWorldProgram = new Dictionary<int, string>
{
[10] = "main() {",
[20] = " printf(\"hello, world\")",
[30] = "}"
};
- 主建構函式的欄位參數不再是 C# 6.0 的一部分。
- 二進位數值型和數位分隔符號 (_) 內的數值型不是目前某些使它釋放到製造的。
有大量功能,不在這裡討論,因為他們已經沾滿了五月篇文章,但除了靜電干擾使用語句 (看到 itl.tc/?p=4038),聲明運算式 (看到 itl.tc/?p=4040) 和異常處理的改進 (看到 itl.tc/?p=4042) 是保持穩定的特點。
總結
顯然,開發商熱衷於 C#,想要確保它保持其卓越。語言小組認真考慮您的回饋意見,修改語言,因為它處理使用者所要說的話。Don不要猶豫去參觀 roslyn.codeplex.com ,讓團隊知道你的想法。另外,別忘了簽出 itl.tc/csharp6 上 C# 6.0 直到它被釋放的更新。
Mark Michaelis 是 IntelliTect 的創始人。他還擔任了首席技術架構師和培訓師。1996 年以來,他一直為 C#、Visual StudioTeam System(VSTS) 和Windows SDK,Microsoft MVP,他被公認為微軟區域主任在 2007 年。 他還擔任幾個 Microsoft 軟體設計-審評組,包括 C#、 連接系統事業部和 VSTS。米迦勒在開發者會議上講話、 寫了許多文章和書籍,和目前正在對下一版的"基本 C#"(艾迪生-Wesley 專業)。
感謝以下的微軟技術專家對本文的審閱:麥斯 Torgersen