代碼演示C#各版本新功能
C#
各版本新功能其實都能在官網搜到,但很少有人整理在一起,並通過非常簡短的代碼將每個新特性演示出來。
- 代碼演示C#各版本新功能
- 附錄/總結
C# 2.0版 - 2005
泛型
Java
中的泛型不支持值類型,且會運行時類型擦除,這一點.NET
更優秀。
// Declare the generic class.
public class GenericList<T>
{
public void Add(T input) { }
}
class TestGenericList
{
private class ExampleClass { }
static void Main()
{
// Declare a list of type int.
GenericList<int> list1 = new GenericList<int>();
list1.Add(1);
// Declare a list of type string.
GenericList<string> list2 = new GenericList<string>();
list2.Add("");
// Declare a list of type ExampleClass.
GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
list3.Add(new ExampleClass());
}
}
分部類型
拆分一個類、一個結構、一個接口或一個方法的定義到兩個或更多的文件中是可能的。 每個源文件包含類型或方法定義的一部分,編譯應用程序時將把所有部分組合起來。
public partial class Employee
{
public void DoWork()
{
}
}
public partial class Employee
{
public void GoToLunch()
{
}
}
匿名方法
Func<int, int, int> sum = delegate (int a, int b) { return a + b; };
Console.WriteLine(sum(3, 4)); // output: 7
可以為null的值類型
double? pi = 3.14;
char? letter = 'a';
int m2 = 10;
int? m = m2;
bool? flag = null;
// An array of a nullable type:
int?[] arr = new int?[10];
迭代器
static void Main()
{
foreach (int number in SomeNumbers())
{
Console.Write(number.ToString() + " ");
}
// Output: 3 5 8
Console.ReadKey();
}
public static System.Collections.IEnumerable SomeNumbers()
{
yield return 3;
yield return 5;
yield return 8;
}
協變和逆變
在 C# 中,協變和逆變能夠實現數組類型、委托類型和泛型類型參數的隱式引用轉換。 協變保留分配兼容性,逆變則與之相反。
// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type.
object obj = str;
// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;
// Contravariance.
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;
C# 3.0版 - 2007
自動實現的屬性
// This class is mutable. Its data can be modified from
// outside the class.
class Customer
{
// Auto-implemented properties for trivial get and set
public double TotalPurchases { get; set; }
public string Name { get; set; }
public int CustomerID { get; set; }
// Constructor
public Customer(double purchases, string name, int ID)
{
TotalPurchases = purchases;
Name = name;
CustomerID = ID;
}
// Methods
public string GetContactInfo() { return "ContactInfo"; }
public string GetTransactionHistory() { return "History"; }
// .. Additional methods, events, etc.
}
class Program
{
static void Main()
{
// Intialize a new object.
Customer cust1 = new Customer(4987.63, "Northwind", 90108);
// Modify a property.
cust1.TotalPurchases += 499.99;
}
}
匿名類型
var v = new { Amount = 108, Message = "Hello" };
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and n .
Console.WriteLine(v.Amount + v.Message);
查詢表達式(LINQ)
LINQ
允許你可以像寫SQL
一樣寫C#
代碼,像這樣:
from p in persons
where p.Age > 18 && p.IsBeatiful
select new
{
p.WeChatId,
p.PhoneNumber
}
LINQ
的意義在於讓C#
做出了重大調整,本章中說到的lambda
表達式、擴展方法、表達式樹、匿名類型、自動屬性等,都是LINQ
的必要組成部分。
由於用擴展方法的形式也能得到一致的結果,而且還能讓代碼風格更加一致,所以我平時用LINQ
語法較少:
// 與上文代碼相同,但改成了擴展方法風格:
persons
.Where(x => x.Age > 18 && x.IsBeatiful)
.Select(x => new
{
x.WeChatId,
x.PhoneNumber,
});
Lambda表達式
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25
表達式樹
這個是LINQ
的基礎之一,它的作用是將代碼像數據一樣,保存在內存中;然后稍后對這些“代碼數據”進行重新解釋/執行。
Entity Framework
就是一個經典場景,它先將表達式樹保存起來,然后執行時,將其翻譯為SQL
發給數據庫執行。
注意:表達式樹並不能表示所有的代碼,
C# 3.0
之后的語法,包含??
、?.
、async await
、可選參數等,都無法放到表達式樹中。據說官方准備更新它,但遲遲沒有進展。
擴展方法
擴展方法使你能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。
static void Main()
{
Console.WriteLine ("Perth".IsCapitalized());
// Equivalent to:
Console.WriteLine (StringHelper.IsCapitalized ("Perth"));
// Interfaces can be extended, too:
Console.WriteLine ("Seattle".First()); // S
}
public static class StringHelper
{
public static bool IsCapitalized (this string s)
{
if (string.IsNullOrEmpty(s)) return false;
return char.IsUpper (s[0]);
}
public static T First<T> (this IEnumerable<T> sequence)
{
foreach (T element in sequence)
return element;
throw new InvalidOperationException ("No elements!");
}
}
var
var i = 10; // Implicitly typed.
int i = 10; // Explicitly typed.
分部方法
namespace PM
{
partial class A
{
partial void OnSomethingHappened(string s);
}
// This part can be in a separate file.
partial class A
{
// Comment out this method and the program
// will still compile.
partial void OnSomethingHappened(String s)
{
Console.WriteLine("Something happened: {0}", s);
}
}
}
對象和集合初始值設定項
public class Cat
{
// Auto-implemented properties.
public int Age { get; set; }
public string Name { get; set; }
public Cat()
{
}
public Cat(string name)
{
this.Name = name;
}
}
C# 4.0版 - 2010
dynamic
這個是特性使得CLR
不得不進行一次修改。有了這個,C#
也能像js
、php
、python
等弱類型語言一樣寫代碼了。
dynamic a = 3;
a = 3.14;
a = "Hello World";
a = new[] { 1, 2, 3, 4, 5 };
a = new Func<int>(() => 3);
a = new StringBuilder();
Console.WriteLine(a.GetType().Name); // StringBuilder
注意dynamic
可以表示任何東西,包含數組、委托等等。濫用dynamic
容易讓程序變得很難維護。
命名參數/可選參數
PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);
public void ExampleMethod(int required, string optionalstr = "default string",
int optionalint = 10)
泛型中的協變和逆變
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
類型等效、內置互操作類型
這個主要是為了和COM
進行交互。之前需要引用一些COM
類型相關的程序集,現在可以直接引用COM
。
具體可以參見:https://docs.microsoft.com/zh-cn/dotnet/framework/interop/type-equivalence-and-embedded-interop-types
C# 5.0版 - 2012
async/await
private DamageResult CalculateDamageDone()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}
calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
async
/await
的本質是狀態機,像IEnumerable<T>
一樣。以前游戲引擎Unity
只支持C# 3.0
,因此當時它用狀態機發Http
請求是用的IEnumerable<T>
。
async
/await
有兩個好處,一是可以避免UI
線程卡頓,二是提高系統吞吐率,最終提高性能。
調用方信息
public void DoProcessing()
{
TraceMessage("Something happened.");
}
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
System.Diagnostics.Trace.WriteLine("message: " + message);
System.Diagnostics.Trace.WriteLine("member name: " + memberName);
System.Diagnostics.Trace.WriteLine("source file path: " + sourceFilePath);
System.Diagnostics.Trace.WriteLine("source line number: " + sourceLineNumber);
}
// Sample Output:
// message: Something happened.
// member name: DoProcessing
// source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs
// source line number: 31
注意這個是編譯期生成的,因此比StackTrace
更能保證性能。
C# 6.0版 - 2015
靜態導入
終於可以不用寫靜態類名了。
using static System.Math;
using static System.Console;
WriteLine(Sin(3.14)); // 0.00159265291648683
異常篩選器
在try-catch
時,可以按指定的條件進行catch
,其它條件不catch
。
public static async Task<string> MakeRequest()
{
WebRequestHandler webRequestHandler = new WebRequestHandler();
webRequestHandler.AllowAutoRedirect = false;
using (HttpClient client = new HttpClient(webRequestHandler))
{
var stringTask = client.GetStringAsync("https://docs.microsoft.com/en-us/dotnet/about/");
try
{
var responseText = await stringTask;
return responseText;
}
catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
{
return "Site Moved";
}
}
}
自動初始化表達式
public ICollection<double> Grades { get; } = new List<double>();
Expression-bodied 函數成員
public override string ToString() => $"{LastName}, {FirstName}";
Null傳播器
var first = person?.FirstName;
字符串內插
public string GetGradePointPercentage() =>
$"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";
nameof
表達式
有時字符串值和某個變量名稱一致,尤其是在做參數驗證時。這里nameof
就能在編譯期,自動從變量名生成一個字符串。
if (IsNullOrWhiteSpace(lastName))
throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
索引初始值設定項
使集合初始化更容易的另一個功能是對 Add 方法使用擴展方法 。 添加此功能的目的是進行 Visual Basic 的奇偶校驗。 如果自定義集合類的方法具有通過語義方式添加新項的名稱,則此功能非常有用。
C# 7.0版本 - 2017
out變量
if (int.TryParse(input, out int result))
Console.WriteLine(result);
else
Console.WriteLine("Could not parse input");
元組和解構
(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");
如上代碼所示,解構可以將元組拆分為多個變量。
模式匹配
現在可以在匹配一個類型時,自動轉換為這個類型的變量,如果轉換失敗,這個變量就賦值為默認值(null
或0
)。
極簡版:
if (input is int count)
sum += count;
switch/case版:
public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
int sum = 0;
foreach (var i in sequence)
{
switch (i)
{
case 0:
break;
case IEnumerable<int> childSequence:
{
foreach(var item in childSequence)
sum += (item > 0) ? item : 0;
break;
}
case int n when n > 0:
sum += n;
break;
case null:
throw new NullReferenceException("Null found in sequence");
default:
throw new InvalidOperationException("Unrecognized type");
}
}
return sum;
}
本地函數
這個主要是方便,javascript
就能這樣寫。
比lambda
的好處在於,這個可以定義在后面,而lambda
必須定義在前面。
public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
if (start < 'a' || start > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
if (end < 'a' || end > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");
if (end <= start)
throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
return alphabetSubsetImplementation();
IEnumerable<char> alphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
yield return c;
}
}
更多的expression-bodied成員
該功能可以讓一些函數寫成表達式的形式,非常的方便。
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
private string label;
// Expression-bodied get / set accessors.
public string Label
{
get => label;
set => this.label = value ?? "Default label";
}
Ref 局部變量和返回結果
此功能允許使用並返回對變量的引用的算法,這些變量在其他位置定義。 一個示例是使用大型矩陣並查找具有某些特征的單個位置。
這個功能主要是為了提高值類型的性能,讓它真正發揮其作用。C++
就有類似的功能。
public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return ref matrix[i, j];
throw new InvalidOperationException("Not found");
}
ref var item = ref MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);
棄元
通常,在進行元組解構或使用
out
參數調用方法時,必須定義一個其值無關緊要且你不打算使用的變量。 為處理此情況,C#
增添了對棄元的支持 。 棄元是一個名為_
的只寫變量,可向單個變量賦予要放棄的所有值。 棄元類似於未賦值的變量;不可在代碼中使用棄元(賦值語句除外)。
using System;
using System.Collections.Generic;
public class Example
{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
}
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}
return ("", 0, 0, 0, 0, 0);
}
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149
二進制文本和數字分隔符
這個用於使數字和二進制更可讀。
// 二進制文本:
public const int Sixteen = 0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;
// 數字分隔符:
public const long BillionsAndBillions = 100_000_000_000;
public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;
throw表達式
throw
之前必須是一個語句,因此有時不得不寫更多的代碼來完成所需功能。但7.0
提供了throw
表達式來使代碼更簡潔,閱讀更輕松。
void Main()
{
// You can now throw expressions in expressions clauses.
// This is useful in conditional expressions:
string result = new Random().Next(2) == 0 ? "Good" : throw new Exception ("Bad");
result.Dump();
Foo().Dump();
}
public string Foo() => throw new NotImplementedException();
C# 8.0 版 - 2019
Readonly 成員
public readonly override string ToString() =>
$"({X}, {Y}) is {Distance} from the origin";
默認接口方法
接口中也能定義方法了,這個新功能經常受到爭論。但想想,有時是先定義接口,而實現接口需要實現很多相關、但又繁瑣的功能,如
ASP.NET Core
中的ILogger
,誰用誰知道,特別多需要實現的方法,但又都差不多。因此所以這個功能其實很有必要。
void Main()
{
ILogger foo = new Logger();
foo.Log (new Exception ("test"));
}
class Logger : ILogger
{
public void Log (string message) => Console.WriteLine (message);
}
interface ILogger
{
void Log (string message);
// Adding a new member to an interface need not break implementors:
public void Log (Exception ex) => Log (ExceptionHeader + ex.Message);
// The static modifier (and other modifiers) are now allowed:
static string ExceptionHeader = "Exception: ";
}
模式匹配增強
這個是為簡化代碼、函數式編程而生的,我個人非常喜歡。
屬性模式
public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
location switch
{
{ State: "WA" } => salePrice * 0.06M,
{ State: "MN" } => salePrice * 0.75M,
{ State: "MI" } => salePrice * 0.05M,
// other cases removed for brevity...
_ => 0M
};
Tuple模式
public static string RockPaperScissors(string first, string second)
=> (first, second) switch
{
("rock", "paper") => "rock is covered by paper. Paper wins.",
("rock", "scissors") => "rock breaks scissors. Rock wins.",
("paper", "rock") => "paper covers rock. Paper wins.",
("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
("scissors", "rock") => "scissors is broken by rock. Rock wins.",
("scissors", "paper") => "scissors cuts paper. Scissors wins.",
(_, _) => "tie"
};
位置模式
static Quadrant GetQuadrant(Point point) => point switch
{
(0, 0) => Quadrant.Origin,
var (x, y) when x > 0 && y > 0 => Quadrant.One,
var (x, y) when x < 0 && y > 0 => Quadrant.Two,
var (x, y) when x < 0 && y < 0 => Quadrant.Three,
var (x, y) when x > 0 && y < 0 => Quadrant.Four,
var (_, _) => Quadrant.OnBorder,
_ => Quadrant.Unknown
};
switch表達式
這個功能能使代碼從大量的if/else
或switch/case
變成“一行代碼”,符合函數式編程的思想,非常好用!
public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
_ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};
using聲明
static int WriteLinesToFile(IEnumerable<string> lines)
{
using var file = new System.IO.StreamWriter("WriteLines2.txt");
// Notice how we declare skippedLines after the using statement.
int skippedLines = 0;
foreach (string line in lines)
{
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
else
{
skippedLines++;
}
}
// Notice how skippedLines is in scope here.
return skippedLines;
// file is disposed here
}
靜態本地函數
相比非靜態本地函數,靜態本地函數沒有閉包,因此生成的代碼更少,性能也更容易控制。
int M()
{
int y = 5;
int x = 7;
return Add(x, y);
static int Add(int left, int right) => left + right;
}
異步流
這個功能和IEnumerable<T>
、Task<T>
對應,一個經典的表格如下:
單值 | 多值 | |
---|---|---|
同步 | T | IEnumerable
|
異步 | Task
|
? |
其中,這個問號?
終於有了答案,它就叫異步流——IAsyncEnumerable<T>
:
public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()
{
for (int i = 0; i < 20; i++)
{
await Task.Delay(100);
yield return i;
}
}
不像IEnumerable<T>
,IAsyncEnumerable<T>
系統還沒有內置擴展方法,因此可能沒有IEnumerable<T>
方便,但是可以通過安裝NuGet
包f
來實現和IEnumerable<T>
一樣(或者更爽)的效果。
索引和范圍
和
Python
中的切片器一樣,只是-
用^
代替了。
var words = new string[]
{
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumped", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
}; // 9 (or words.Length) ^0
var quickBrownFox = words[1..4];
var lazyDog = words[^2..^0];
var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
Null合並賦值
List<int> numbers = null;
int? i = null;
numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);
Console.WriteLine(string.Join(" ", numbers)); // output: 17 17
Console.WriteLine(i); // output: 17
非托管構造類型
與任何非托管類型一樣,可以創建指向此類型的變量的指針,或針對此類型的實例在堆棧上分配內存塊
Span<Coords<int>> coordinates = stackalloc[]
{
new Coords<int> { X = 0, Y = 0 },
new Coords<int> { X = 0, Y = 3 },
new Coords<int> { X = 4, Y = 0 }
};
嵌套表達式中的 stackalloc
Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };
var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6 ,8 });
Console.WriteLine(ind); // output: 1
附錄/總結
這么多功能,你印象最深刻的是哪個呢?
參考資料:C#發展歷史 - C#指南 | Microsoft Docs https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-version-history
本文內容和代碼由肖鵬整理,有修改;轉載已獲得肖鵬本人授權。肖鵬是我公司從
Java
轉.NET
的同事。原文鏈接為:https://akiyax.github.io/new-features-in-csharp/。
喜歡的朋友請關注我的微信公眾號:【DotNet騷操作】