雖然現在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的發布吧
