C# 二十年語法變遷之 C# 2,C# 3 ,C# 4參考
https://benbowen.blog/post/two_decades_of_csharp_i/
自從 C# 於 2000 年推出以來,該語言的規模已經大大增加,我不確定任何人是否有可能在任何時候都對每一種語言特性都有深入的瞭解。因此,我想寫一系列快速參考文章,總結自 C# 2.0 以來所有主要的新語言特性。我不會詳細介紹它們中的任何一個,但我希望這個系列可以作爲我自己(希望你也是!)的參考,我可以不時回過頭來記住我使用的工具工具箱裏有。😃
開始之前的一個小提示:我將跳過一些更基本的東西(例如 C# 2.0 引入了泛型,但它們的使用範圍如此廣泛,以至於它們不值得包括在內);而且我還可以將一些功能“粘合”在一起,以使其更簡潔。本系列並不打算成爲該語言的權威或歷史記錄。相反,它更像是可能派上用場的重要語言功能的“備忘單”。
C# 2.0
可空值類型
這些允許您將null指定爲任何結構變量的潛在值(否則null將無效):
class MyClass {
public int MyInt { get; }
public int? MyNullableInt { get; } // This property can be null even though it's of type 'int'
public MyClass(int? input) { // input can be null
MyInt = input != null ? input.Value : 0; // .Value throws an exception when accessed if input is null
MyNullableInt = input;
}
}
// ..
static void Test() {
var mc = new MyClass(null);
if (mc.MyNullableInt == null) Console.WriteLine("Was null!");
}
• “可空值類型”
從技術上講,任何可空值對象的類型都是Nullable
部分類型partial class
此功能允許將大型類型(類、接口或結構)的實現分散到多個文件中。
// File: ExampleClass.Alpha.cs
public partial class ExampleClass {
public void DoAlphaOne() { ... }
public void DoAlphaTwo() { ... }
}
// File: ExampleClass.Beta.cs
public partial class ExampleClass {
public void DoBetaOne() { ... }
public void DoBetaTwo() { ... }
}
// Elsewhere
static void Test() {
var ec = new ExampleClass();
ec.DoAlphaOne();
ec.DoBetaTwo();
// etc
}
• “部分類”
空值合併
此功能允許創建計算爲鏈中第一個非空值的表達式:
var neverNullString = _stringField ?? stringParameter ?? "Default";
全屏查看代碼• “Null Coalesceing”
此代碼會將neverNullString設置爲_stringField,除非_stringField爲空;在這種情況下,它將把它設置爲stringParameter,除非stringParameter也爲空,在這種情況下,它將把它設置爲文字值"Default"。
迭代器生成器(Yield)
此功能允許您通過在可枚舉中“生成”元素來創建IEnumerable
public IEnumerable<int> GetOneTwoThree(bool includeNegative = false) {
yield return 1;
yield return 2;
yield return 3;
if (!includeNegative) yield break;
yield return -1;
yield return -2;
yield return -3;
}
// ..
Console.WriteLine(String.Join(",", GetOneTwoThree())); // Prints "1,2,3" on console
Console.WriteLine(String.Join(",", GetOneTwoThree(true))); // Prints "1,2,3,-1,-2,-3" on console
全屏查看代碼• “收益回報和中斷”
C# 3.0
擴展方法
此功能允許在預先存在的類型上定義新方法。這對於向您無法控制的類型添加功能很有用。以下示例顯示
如何將ToString()的重載添加到double類型:
public static class DoubleExtensions {
public static string ToString(this double @this, int numDecimalPlaces) {
return @this.ToString("N" + numDecimalPlaces.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
}
}
// ... Elsewhere ...
// Will write something like "3.5"; assuming YearsWorkedAtCompany is a double:
Console.WriteLine(user.YearsWorkedAtCompany.ToString(1));
• “ ToString 擴展”
集合初始化器
此功能允許實例化各種集合類型,同時向它們添加初始值:
var myArray = new[] { 1, 2, 3, 4, 5 }; // myArray is an int[] of length 5
var myList = new List<int> { 1, 2, 3, 4, 5 }; // myList is a List<int> with 5 elements
var myDict = new Dictionary<int, int> { { 1, 100 }, { 2, 200 } }; // myDict is a Dictionary with 2 key-value pairs
• “集合初始化器”
對象初始化器
此功能允許在其實例化點設置對象的內聯屬性:
class Person {
public string Name { get; set; }
public int Age { get; set; }
}
static void Test() {
var person = new Person {
Name = "Ben",
Age = 30
};
}
• “對象初始化器”
Partial Methods
與部分類一樣,這允許您在不同的文件中編寫方法的兩個或多個部分。不保證執行順序。該方法必須具有void返回類型,並且必須是私有的。
// File: ExampleClass.Alpha.cs
public partial class ExampleClass {
public void Print() => DoThing();
partial void DoThing() {
Console.WriteLine("AAA");
}
}
// File: ExampleClass.Beta.cs
public partial class ExampleClass {
partial void DoThing() {
Console.WriteLine("BBB");
}
}
// Elsewhere
static void Test() {
var ec = new ExampleClass();
ec.Print(); // Prints AAA and BBB to console (order unspecified).
}
• “部分方法”
自動生成的代碼可以使用空的部分方法聲明,以允許用戶在需要時手動插入自定義邏輯:
// File: ExampleClass.AutoGenerated.cs
public partial class ExampleClass {
public void SomeAutoGeneratedMethod() {
DoSomethingOnAutoGenMethodCall();
// do other stuff
}
partial void DoSomethingOnAutoGenMethodCall(); // If user does not supply implementation in ExampleClass.User.cs this method will not even be compiled and calls to it will be removed
}
// File: ExampleClass.User.cs
public partial class ExampleClass {
partial void DoSomethingOnAutoGenMethodCall() {
Console.WriteLine("SomeAutoGeneratedMethod Invoked");
}
}
• “自動生成代碼的部分方法”
C# 4.0
動態/後期綁定類型
引入了動態類型以允許“後期綁定”類型解析。我偶爾使用動態的一件事是作爲一種更簡潔的反射形式:
class GenericClass<T1, T2> {
public T1 SomeComplexMethod<T3>(T2 inputA, T3 inputB) {
// ...
}
}
class Program {
static void Main() {
var gc = new GenericClass<int, string>();
var resultA = InvokeComplexMethodReflectively(gc, "Hi", 3f);
var resultB = InvokeComplexMethodDynamically(gc, "Hi", 3f);
Console.WriteLine(resultA);
Console.WriteLine(resultB);
}
static object InvokeComplexMethodReflectively(object genericClassInstance, string inputA, float inputB) {
var openMethodDefinition = genericClassInstance.GetType().GetMethod("SomeComplexMethod");
var genericMethodDefinition = openMethodDefinition.MakeGenericMethod(typeof(float));
return genericMethodDefinition.Invoke(genericClassInstance, new object[] { inputA, inputB });
}
static object InvokeComplexMethodDynamically(object genericClassInstance, string inputA, float inputB) {
return ((dynamic) genericClassInstance).SomeComplexMethod(inputA, inputB);
}
}
• “動態替代反射”
很多人根本就對使用動態有所保留,但是後期綁定無論如何都是在內部使用反射解決的,所以它真的可以被認爲是反射的語法糖。此外,由於綁定信息的緩存,動態實際上通常可以勝過使用“純”反射的相同方法。
C# 還添加了一個名爲ExpandoObject的新類型,它類似於字典,但鍵是動態添加的成員:
static void Test() {
dynamic user = new ExpandoObject();
user.Name = "Ben";
user.Age = 30;
// Prints "User Name is Ben" and "User Age is 30"
foreach (var kvp in (IDictionary<String, Object>) user) {
Console.WriteLine($"User {kvp.Key} is {kvp.Value}");
}
}
• “ExpandoObject”
可選參數
可選參數是具有指定默認值的方法的參數,因此不需要由調用者指定:
// 'nickname' is optional here
public static void MethodWithOptionalArgs(string name, int age, string nickname = null) {
// ...
}
static void Test() {
MethodWithOptionalArgs("Ben", 30); // No nickname specified, 'null' will be passed in
}
• “可選參數”
可選參數必須始終排在參數列表的最後。
命名參數
C# 4.0 中的命名參數允許在有多個參數時指定特定的可選參數:
public static void MethodWithOptionalArgs(string name, int age, string nickname = null, bool married = false, string address = null) {
// ...
}
static void Test() {
MethodWithOptionalArgs("Ben", 30, address: "Noveria"); // 'nickname' and 'married' are left unspecified
}
• “可選參數”
從 C# 7.2 開始,即使參數不是可選的,也可以命名。我偶爾在指定一個布爾參數時使用它,否則它很難理解:
static void Test() {
CreateTables(true); // What is true?
CreateTables(deletePreviousData: true); // Ahh... Much better.
}
• “可選參數”
泛型類型參數的協方差和逆變
在使用泛型類型參數創建接口或委託類型時,我們可以指定這些類型參數是協變的或逆變的:
interface ICovariant<out T> { }
interface IContravariant<in T> { }
static void Test() {
ICovariant<object> covariantObj;
ICovariant<int> covariantInt = GetCovariant<int>();
covariantObj = covariantInt; // "out T" in ICovariant allows this
IContravariant<object> contravariantObj = GetContravariant<object>();
IContravariant<int> contravariantInt;
contravariantInt = contravariantObj; // "in T" in IContravariant allows this
}
• “接口中的協變和逆變通用參數”
delegate T Covariant<out T>();
delegate void Contravariant<in T>(T value);
static void Test() {
Covariant<object> covariantObj;
Covariant<int> covariantInt = () => 3;
covariantObj = covariantInt;
Contravariant<object> contravariantObj = myObj => Console.WriteLine(myObj);
Contravariant<int> contravariantInt;
contravariantInt = contravariantObj;
}
• “委託中的協變和逆變通用參數”
當指定通常作爲接口輸出 的對象類型時,協方差很有用;因爲我們通常不介意實際的輸出類型是否是指定類型的子類型。在指定通常作爲接口輸入
的對象類型時,逆變很有用。因爲我們通常不介意預期的輸入類型是否是給定類型的父類型。 與其記住“contra”和“co”方差之間的區別,我發現記住in用於輸入而out用於輸出(通常,無論如何)是有用的。