C# 10 完整特性介紹
前言:
C#使其擁有強如 Haskell 、Rust 的表達能力,不僅能提供從頭到尾的跨程序集的靜態(tài)類型支持,還能做到像動態(tài)類型語言那樣的靈活。邏輯代碼是類型的證明,只有類型系統(tǒng)強大了,代碼編寫起來才能更順暢、更不容易出錯。
1、record struct
首先自然是 record struct,解決了 record 只能給 class 而不能給 struct 用的問題:
1
|
record struct Point( int X, int Y); |
用 record 定義 struct 的好處其實有很多,例如你無需重寫 GetHashCode
和 Equals
之類的方法了。
2、sealed record ToString 方法
之前 record 的 ToString 是不能修飾為 sealed
的,因此如果你繼承了一個 record,相應的 ToString 行為也會被改變,因此這是個虛方法。
但是現(xiàn)在你可以把 record 里的 ToString 方法標記成 sealed
,這樣你的 ToString
方法就不會被重寫了。
3、struct 無參構造函數(shù)
一直以來 struct 不支持無參構造函數(shù),現(xiàn)在支持了:
1
2
3
4
5
|
struct Foo { public int X; public Foo() { X = 1; } } |
但是使用的時候就要注意了,因為無參構造函數(shù)的存在使得 new struct()
和 default(struct)
的語義不一樣了,例如 new Foo().X == default(Foo).X
在上面這個例子中將會得出 false
。
4、匿名對象的 with
可以用 with 來根據(jù)已有的匿名對象創(chuàng)建新的匿名對象了:
1
2
|
var x = new { A = 1, B = 2 }; var y = x with { A = 3 }; |
這里 y.A 將會是 3 。
5、全局的 using
利用全局 using 可以給整個項目啟用 usings,不再需要每個文件都寫一份。比如你可以創(chuàng)建一個 Import.cs,然后里面寫:
1
2
|
using System; using i32 = System.Int32; |
然后你整個項目都無需再 using System,并且可以用 i32 了。
6、文件范圍的 namespace
這個比較簡單,以前寫 namespace 還得帶一層大括號,以后如果一個文件里只有一個 namespace 的話,那直接在最上面這樣寫就行了:
1
|
namespace MyNamespace; |
7、常量字符串插值
你可以給 const string 使用字符串插值了,非常方便:
1
2
|
const string x = "hello" ; const string y = $ "{x}, world!" ; |
8、lambda 改進
這個改進可以說是非常大,我分多點介紹。
8.1. 支持 attributes
lambda 可以帶 attribute 了:
1
2
3
|
f = [Foo] (x) => x; // 給 lambda 設置 f = [ return : Foo] (x) => x; // 給 lambda 返回值設置 f = ([Foo] x) => x; // 給 lambda 參數(shù)設置 |
8.2. 支持指定返回值類型
此前 C# 的 lambda 返回值類型靠推導,C# 10 開始允許在參數(shù)列表最前面顯示指定 lambda 類型了:
1
|
f = int () => 4; |
8.3. 支持 ref 、in 、out 等修飾
1
|
f = ref int ( ref int x) => ref x; // 返回一個參數(shù)的引用 |
8.4. 頭等函數(shù)
函數(shù)可以隱式轉換到 delegate,于是函數(shù)上升至頭等函數(shù):
1
2
3
|
void Foo() { Console.WriteLine( "hello" ); } var x = Foo; x(); // hello |
8.5. 自然委托類型
lambda 現(xiàn)在會自動創(chuàng)建自然委托類型,于是不再需要寫出類型了。
1
2
3
|
var f = () => 1; // Func<int> var g = string ( int x, string y) => $ "{y}{x}" ; // Func<int, string, string> var h = "test" .GetHashCode; // Func<int> |
9、CallerArgumentExpression
現(xiàn)在,CallerArgumentExpression
這個 attribute 終于有用了。借助這個 attribute
,編譯器會自動填充調用參數(shù)的表達式字符串,例如:
1
2
3
4
|
void Foo( int value, [CallerArgumentExpression( "value" )] string ? expression = null ) { Console.WriteLine(expression + " = " + value); } |
當你調用 Foo(4 + 5)
時,會輸出 4 + 5 = 9
。這對測試框架極其有用,因為你可以輸出 assert 的原表達式了:
1
2
3
4
|
static void Assert( bool value, [CallerArgumentExpression( "value" )] string ? expr = null ) { if (!value) throw new AssertFailureException(expr); } |
10、tuple 支持混合定義和使用
比如:
1
2
|
int y = 0; (var x, y, var z) = (1, 2, 3); |
于是 y 就變成 2 了,同時還創(chuàng)建了兩個變量 x 和 z,分別是 1 和 3 。
11、接口支持抽象靜態(tài)方法
這個特性將會在 .NET 6 作為 preview 特性放出,意味著默認是不啟用的,需要設置 <LangVersion>preview</LangVersion> 和 <EnablePreviewFeatures>true</EnablePreviewFeatures>,然后引入一個官方的 nuget 包 System.Runtime.Experimental 來啟用。
然后接口就可以聲明抽象靜態(tài)成員了,.NET 的類型系統(tǒng)正式具備虛靜態(tài)方法分發(fā)能力。
例如,你想定義一個可加而且有零的接口 IMonoid<T>:
1
2
3
4
5
|
interface IMonoid<T> where T : IMonoid<T> { abstract static T Zero { get ; } abstract static T operator +(T l, T r); } |
然后可以對其進行實現(xiàn),例如這里的 MyInt:
1
2
3
4
5
6
7
8
9
|
public class MyInt : IMonoid<MyInt> { public MyInt( int val) { Value = val; } public static MyInt Zero { get ; } = new MyInt(0); public static MyInt operator +(MyInt l, MyInt r) => new MyInt(l.Value + r.Value); public int Value { get ; } } |
然后就能寫出一個方法對 IMoniod<T> 進行求和了,這里為了方便寫成擴展方法:
1
2
3
4
5
6
7
8
9
|
public static class IMonoidExtensions { public static T Sum<T>( this IEnumerable<T> t) where T : IMonoid<T> { var result = T.Zero; foreach (var i in t) result += i; return result; } } |
最后調用:
1
2
|
List<MyInt> list = new () { new (1), new (2), new (3) }; Console.WriteLine(list.Sum().Value); // 6 |
你可能會問為什么要引入一個 System.Runtime.Experimental
,因為這個包里面包含了 .NET 基礎類型的改進:給所有的基礎類型都實現(xiàn)了相應的接口,比如給數(shù)值類型都實現(xiàn)了 INumber<T>,
給可以加的東西都實現(xiàn)了 IAdditionOperators<TLeft
, TRight, TResult>
等等,用起來將會非常方便,比如你想寫一個函數(shù),這個函數(shù)用來把能相加的東西加起來:
1
2
3
4
|
T Add<T>(T left, T right) where T : IAdditionOperators<T, T, T> { return left + right; } |
就搞定了。
接口的靜態(tài)抽象方法支持和未來 C# 將會加入的 shape 特性是相輔相成的,屆時 C# 將利用 interface 和 shape 支持 Haskell 的 class
、Rust 的 trait
那樣的 type classes,將類型系統(tǒng)上升到一個新的層次。
12、泛型 attribute
是的你沒有看錯,C# 的 attributes 支持泛型了,不過 .NET 6 中將以預覽特定放出,因此需要 <LangVersion>preview</LangVersion>:
1
2
3
4
5
|
class TestAttribute<T> : Attribute { public T Data { get ; } public TestAttribute(T data) { Data = data; } } |
然后你就能這么用了:
1
2
3
|
[Test< int >(3)] [Test< float >(4.5f)] [Test< string >( "hello" )] |
13、允許在方法上指定 AsyncMethodBuilder
C# 10 將允許方法上使用 [AsyncMethodBuilder(...)]
來使用你自己實現(xiàn)的 async method builder,代替自帶的 Task
或者 ValueTask
的異步方法構造器。這也有助于你自己實現(xiàn)零開銷的異步方法。
14、line 指示器支持行列和范圍
以前 #line
只能用來指定一個文件中的某一行,現(xiàn)在可以指定行列和范圍了,這對寫編譯器和代碼生成器的人非常有用:
1
2
3
|
#line (startLine, startChar) - (endLine, endChar) charOffset "fileName" // 比如 #line (1, 1) - (2, 2) 3 "test.cs" |
15、嵌套屬性模式匹配改進
以前在匹配嵌套屬性的時候需要這么寫:
1
|
if (a is { X: { Y: { Z: 4 } } }) { ... } |
現(xiàn)在只需要簡單的:
1
|
if (a is { X.Y.Z: 4 }) { ... } |
就可以了
15改進的字符串插值
以前 C# 的字符串插值是很粗暴的 string.Format,并且對于值類型參數(shù)來說會直接裝箱,對于多個參數(shù)而言還會因此而分配一個數(shù)組(比如 string.Format("{} {}", a, b
) 其實是 string.Format("{} {}", new object [] { (object)a, (object)b })),
這很影響性能。現(xiàn)在字符串插值被改進了:
1
2
|
var x = 1; Console.WriteLine($ "hello, {x}" ); |
會被編譯成:
1
2
3
4
5
|
int x = 1; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(7, 1); defaultInterpolatedStringHandler.AppendLiteral( "hello, " ); defaultInterpolatedStringHandler.AppendFormatted(x); Console.WriteLine(defaultInterpolatedStringHandler.ToStringAndClear()); |
上面這個 DefaultInterpolatedStringHandler
也可以借助 InterpolatedStringHandler
這個 attribute 替換成你自己實現(xiàn)的插值處理器,來決定要怎么進行插值。借助這些可以實現(xiàn)接近零開銷的字符串插值。
Source Generator v2:
代碼生成器在 C# 10 將會迎來 v2 版本,這個版本包含很多改進,包括強類型的代碼構建器,以及增量編譯的支持等等
到此這篇關于C# 10 完整特性詳情的文章就介紹到這了,更多相關C# 10 完整特性內容請搜索服務器之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.cnblogs.com/hez2010/p/whats-new-in-csharp-10.html