虽然现在C# 7才发布不久,并且新的版本和特性还在增加中,但是C# 8.0已经为大家公开了一些未来可能出现的新特性。
*注:以下特性只是计划,可能在将来的正式版本会有一些差异
1.Nullable Reference Types
该特性其实本来计划在C#7.x中就引入,但是却被推迟到了下一个版本中。目的是为了避免引用为null的时候而导致的错误。
其核心思想是允许变量类型定义指定是否可以为它们分配空值:
1 IWeapon? canBeNull; 2 IWeapon cantBeNull;
1
2
3
|
canBeNull = null ; // no warning
cantBeNull = null ; // warning
cantBeNull = canBeNull; // warning
|
此时当申明可为nullable的对象赋值为null的时候,编译器就不会提示警告。
1
2
3
4
5
|
canBeNull.Repair(); // warning
cantBeNull.Repair(); // no warning
if (canBeNull != null ) {
cantBeNull.Repair(); // no warning
} |
2.Records
records是一个新的语法糖,它简化了原来创建简单类的过程,通过一条语句就可以创建出一个标准的C# 类。
例如下面的代码:
1 |
public class Sword( int Damage, int Durability);
|
它相对于原来的写法是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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的特性)
1 |
var (damage, durability) = sword;
|
最后的一个With方法可以供我们创建一个不同属性值的Sword副本对象。
1 |
var (damage, durability) = sword;
|
当然,对于With的方法,C# 也提供了一个语法糖写法:
1 |
var strongerSword = sword with { Damage = 8 };
|
3.Default Interface Methods
在以往的C# 语法中,我们都知道一个Interface只能够申明方法体,却不能对其进行实现:
1
2
3
4
5
|
interface ISample
{ void M1(); // allowed
void M2() => Console.WriteLine( "ISample.M2" ); // not allowed
} |
按照以往的写法,我们一般是尝试写一些抽象类来作为替代实现:
1
2
3
4
5
|
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 接口:
1
2
3
4
5
6
7
8
9
10
|
public interface IAsyncEnumerable< out T>
{ IAsyncEnumerator<T> GetAsyncEnumerator();
} public interface IAsyncEnumerator< out T> : IAsyncDisposable
{ Task< bool > MoveNextAsync();
T Current { get ; }
} |
此外,使用异步迭代器还需要IDisposable接口的异步版本:
1
2
3
4
|
public interface IAsyncDisposable
{ Task DisposeAsync();
} |
接下来,在使用的时候,可能看上去就像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var enumerator = enumerable.GetAsyncEnumerator();
try { while (await enumerator.WaitForNextAsync())
{
while ( true )
{
Use(enumerator.Current);
}
}
} finally { await enumerator.DisposeAsync();
} |
当然,这个写法对我们C#的开发人员来说可能还不是太眼熟,因为在传统的迭代器写法上,我们已经习惯了Foreach的写法,因此对于异步迭代器来说,它也会存在对应的一个foreach版本,就如同下面这样:
1
2
3
4
|
foreach await ( var item in enumerable)
{ Use(item);
} |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
async IAsyncEnumerable< int > AsyncIterator()
{ try
{
for ( int i = 0; i < 100; i++)
{
yield await GetValueAsync(i);
}
}
finally
{
await HandleErrorAsync();
}
} |
5.Ranges
这个特性可能相对来说就比较有趣了,它允许我们使用简短的语法来定义一个区间值,比如:
1 |
var range = 1..5;
|
这样就产生了一个表示已声明范围的结构:
1
2
3
4
5
6
7
8
|
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...
} |
在实际的应用过程中,我们可以这样来使用它:
1
2
3
4
5
6
7
|
Span<T> this [Range range]
{ get
{
return ((Span<T>) this ).Slice(start: range.Start, length: range.End - range.Start);
}
} |
1
2
3
4
|
foreach ( var index in min..max)
{ // process values
} |
1
2
3
4
5
6
|
switch (value)
{ case 1..5:
// value in range
break ;
} |
这个特性看上去果然非常的good。
6.Generic Attributes
对泛型特性的支持将为需要类型作为参数的属性提供更好的语法。目前,只能使用以下语法将类型传递给特性:
1
2
3
4
5
6
7
|
public class TypedAttribute : Attribute
{ public TypedAttribute(Type type)
{
// ...
}
} |
当有了泛型特性之后,我们可以尝试这样做:
1
2
3
4
5
6
7
|
public class TypedAttribute<T> : Attribute
{ public TypedAttribute()
{
// ...
}
} |
1
2
3
4
|
public TypedAttribute(T value)
{ // ...
} |
7.Default Literal in Deconstruction
在C# 7.x中引入了default 默认值和解构的概念。在C# 8中将实现两者的共同作用。
要为C#7中的元组的所有成员分配默认值,必须使用元组赋值语法:
1 |
( int x, int y) = ( default , default );
|
通过支持解构语法中的默认文字,以下语法也可以实现相同的功能:
1 |
( int x, int y) = default ;
|
8.Caller Argument Expression
在C#5中,引入了CallerMemberName, CallerFilePath and CallerLineNumber特性,方便我们能够获取到有关调用方法的一些信息。
就像CallerMemberName在INotifyPropertyChanged中的应用,对于WPF开发的童鞋就在熟悉不过了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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的特性,它捕获调用方法中的参数:
1
2
3
4
5
6
7
8
9
10
11
|
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
这可能也将成为将来常用的一个新特性,它将更加简化在申明时候的类型推断。
比如以往我们申明一个对象是这个样子的:
1
2
|
Dictionary< string , string > dictionary = new Dictionary< string , string >(); // without var keyword
var dictionary = new Dictionary< string , string >(); // with var keyword
|
但是在C#8中,将简化成这样:
1
2
3
4
5
|
class DictionaryWrapper
{ private Dictionary< string , string > dictionary = new ();
// ...
} |
Over:
当然距离C#8真是发布可能还要等一段时间,期间可能也会增加一些其他的特性,真正的体验效果还是一起期待8.0的发布吧