C# 8.0的計劃特性
雖然現在C# 7才發布不久,並且新的版本和特性還在增加中,但是C# 8.0已經為大家公開了一些未來可能出現的新特性。
*註:以下特性只是計劃,可能在將來的正式版本會有一些差異
1.Nullable Reference Types
該特性其實本來計劃在C#7.x中就引入,但是卻被推遲到了下一個版本中。目的是為了避免引用為null的時候而導致的錯誤。
其核心思想是允許變量類型定義指定是否可以為它們分配空值:
1 IWeapon? canBeNull; 2 IWeapon cantBeNull;
canBeNull = null; // no warning cantBeNull = null; // warning cantBeNull = canBeNull; // warning
此時當申明可為nullable的對象賦值為null的時候,編譯器就不會提示警告。
canBeNull.Repair(); // warning cantBeNull.Repair(); // no warning if (canBeNull != null) { cantBeNull.Repair(); // no warning }
2.Records
records是一個新的語法糖,它簡化了原來創建簡單類的過程,通過一條語句就可以創建出一個標準的C# 類。
例如下面的代碼:
public class Sword(int Damage, int Durability);
它相對於原來的寫法是:
public class Sword : IEquatable<Sword> { public int Damage { get; } public int Durability { get; } public Sword(int Damage, int Durability) { this.Damage = Damage; this.Durability = Durability; } public bool Equals(Sword other) { return Equals(Damage, other.Damage) && Equals(Durability, other.Durability); } public override bool Equals(object other) { return (other as Sword)?.Equals(this) == true; } public override int GetHashCode() { return (Damage.GetHashCode() * 17 + Durability.GetHashCode()); } public void Deconstruct(out int Damage, out int Durability) { Damage = this.Damage; Durability = this.Durability; } public Sword With(int Damage = this.Damage, int Durability = this.Durability) => new Sword(Damage, Durability); }
上面的代碼段可以看出,該類具有只讀屬性和初始化它們的構造函數。它實現值的比較,並且重寫了GetHashCode,以便在基於哈希的集合中使用,如Dictionary 和 Hashtable。
同時我們還看到在倒數第二個方法是一個解構的方法,它允許我們將Record所創建的對象進行解構為一個元組(關於解構的特性,可以參加C#7.0的特性)
var (damage, durability) = sword;
最後的一個With方法可以供我們創建一個不同屬性值的Sword副本對象。
var (damage, durability) = sword;
當然,對於With的方法,C# 也提供了一個語法糖寫法:
var strongerSword = sword with { Damage = 8 };
3.Default Interface Methods
在以往的C# 語法中,我們都知道一個Interface只能夠申明方法體,卻不能對其進行實現:
interface ISample { void M1(); // allowed void M2() => Console.WriteLine("ISample.M2"); // not allowed }
按照以往的寫法,我們一般是嘗試寫一些抽象類來作為替代實現:
abstract class SampleBase { public abstract void M1(); public void M2() => Console.WriteLine("SampleBase.M2"); }
但在C# 8.0中可能引入接口的方法實現功能。
4.Asynchronous Streams
C# 目前是已經支持了叠代器( iterators ) 和 異步方法。在C#8.0中打算結合現有的兩者,推出異步的叠代器,它將基於異步的 IEnumerable 和 IEnumerator 接口:
public interface IAsyncEnumerable<out T> { IAsyncEnumerator<T> GetAsyncEnumerator(); } public interface IAsyncEnumerator<out T> : IAsyncDisposable { Task<bool> MoveNextAsync(); T Current { get; } }
此外,使用異步叠代器還需要IDisposable接口的異步版本:
public interface IAsyncDisposable { Task DisposeAsync(); }
接下來,在使用的時候,可能看上去就像下面這樣:
var enumerator = enumerable.GetAsyncEnumerator(); try { while (await enumerator.WaitForNextAsync()) { while (true) { Use(enumerator.Current); } } } finally { await enumerator.DisposeAsync(); }
當然,這個寫法對我們C#的開發人員來說可能還不是太眼熟,因為在傳統的叠代器寫法上,我們已經習慣了Foreach的寫法,因此對於異步叠代器來說,它也會存在對應的一個foreach版本,就如同下面這樣:
foreach await (var item in enumerable) { Use(item); }
async IAsyncEnumerable<int> AsyncIterator() { try { for (int i = 0; i < 100; i++) { yield await GetValueAsync(i); } } finally { await HandleErrorAsync(); } }
5.Ranges
這個特性可能相對來說就比較有趣了,它允許我們使用簡短的語法來定義一個區間值,比如:
var range = 1..5;
這樣就產生了一個表示已聲明範圍的結構:
struct Range : IEnumerable<int> { public Range(int start, int end); public int Start { get; } public int End { get; } public StructRangeEnumerator GetEnumerator(); // overloads for Equals, GetHashCode... }
在實際的應用過程中,我們可以這樣來使用它:
Span<T> this[Range range] { get { return ((Span<T>)this).Slice(start: range.Start, length: range.End - range.Start); } }
foreach (var index in min..max) { // process values }
switch (value) { case 1..5: // value in range break; }
這個特性看上去果然非常的good。
6.Generic Attributes
對泛型特性的支持將為需要類型作為參數的屬性提供更好的語法。目前,只能使用以下語法將類型傳遞給特性:
public class TypedAttribute : Attribute { public TypedAttribute(Type type) { // ... } }
當有了泛型特性之後,我們可以嘗試這樣做:
public class TypedAttribute<T> : Attribute { public TypedAttribute() { // ... } }
public TypedAttribute(T value) { // ... }
7.Default Literal in Deconstruction
在C# 7.x中引入了default 默認值和解構的概念。在C# 8中將實現兩者的共同作用。
要為C#7中的元組的所有成員分配默認值,必須使用元組賦值語法:
(int x, int y) = (default, default);
通過支持解構語法中的默認文字,以下語法也可以實現相同的功能:
(int x, int y) = default;
8.Caller Argument Expression
在C#5中,引入了CallerMemberName, CallerFilePath and CallerLineNumber特性,方便我們能夠獲取到有關調用方法的一些信息。
就像CallerMemberName在INotifyPropertyChanged中的應用,對於WPF開發的童鞋就在熟悉不過了:
class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private int property; public int Property { get { return property; } set { if (value != property) { property = value; OnPropertyChanged(); } } } }
在C#8中可能會引入一個叫做CallerArgumentExpression的特性,它捕獲調用方法中的參數:
public Validate(int[] array, [CallerArgumentExpression("array")] string arrayExpression = null) { if (array == null) { throw new ArgumentNullException(nameof(array), $"{arrayExpression} was null."); } if (array.Length == 0) { throw new ArgumentException($"{arrayExpression} was empty.", nameof(array)); } }
9.Target-typed new Expression
這可能也將成為將來常用的一個新特性,它將更加簡化在申明時候的類型推斷。
比如以往我們申明一個對象是這個樣子的:
Dictionary<string, string> dictionary = new Dictionary<string, string>(); // without var keyword var dictionary = new Dictionary<string, string>(); // with var keyword
但是在C#8中,將簡化成這樣:
class DictionaryWrapper { private Dictionary<string, string> dictionary = new(); // ... }
Over:
當然距離C#8真是發布可能還要等一段時間,期間可能也會增加一些其他的特性,真正的體驗效果還是一起期待8.0的發布吧
C# 8.0的計劃特性