Skip to content

C# 7.3 中的新增功能

🏷️ C# C# 新增功能

官方文档见 这里。本文在其基础上添加了一些自己理解及示例代码,如有不正确的地方欢迎指正。

C# 7.3 版本有两个主要主题。第一个主题提供使安全代码的性能与不安全代码的性能一样好的功能。第二个主题提供对现有功能的增量改进。此外,在此版本中添加了新的编译器选项。

启用更高效的安全代码

索引 fixed 字段不需要进行固定

简单点来说就是之前使用 fixed 变量才能访问 fixed 字段(示例中的 myFixedField 字段)。

csharp
fixed (int* ptr = s.myFixedField)
{
    int p = ptr[5];
}

从 7.3 开始可以直接使用了。

csharp
int p = s.myFixedField[5];

完整示例代码:

csharp
unsafe struct S
{
    public fixed int myFixedField[10];
}

class C
{
    static S s = new S();

    unsafe public void M()
    {
        int p = s.myFixedField[5];
    }
}

关于 fixed 关键字,更多信息请参考 fixed 语句

可以重新分配 ref 局部变量

现在,在对 ref 局部变量进行初始化后,可以对其重新分配,以引用不同的实例。

csharp
ref VeryLargeStruct refLocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

上述代码在 C# 7.2 中会报如下错误:

功能“引用重新赋值”在 C# 7.2 中不可用。请使用 7.3 或更高的语言版本。

stackalloc 数组支持初始值设定项

如标题所述,支持在定义变量时支持设置初始值。例:

csharp
int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};

上述代码在 C# 7.2 中会报如下错误:

功能“stackalloc 初始值设定项”在 C# 7.2 中不可用。请使用 7.3 或更高的语言版本。

关于 stackalloc 请参考 stackalloc 运算符

更多类型支持 fixed 语句

C# 7.3 开始,任何包含返回 ref Tref readonly TGetPinnableReference() 方法的类型均有可能为 fixed

添加此功能意味着 fixed 可与 System.Span<T> 和相关类型配合使用。示例如下:

csharp
unsafe private static void FixedSpanExample()
{
    int[] PascalsTriangle = {
                  1,
                1,  1,
              1,  2,  1,
            1,  3,  3,  1,
          1,  4,  6,  4,  1,
        1,  5,  10, 10, 5,  1
    };

    Span<int> RowFive = new Span<int>(PascalsTriangle, 10, 5);

    fixed (int* ptrToRow = RowFive)
    {
        // Sum the numbers 1,4,6,4,1
        var sum = 0;
        for (int i = 0; i < RowFive.Length; i++)
        {
            sum += *(ptrToRow + i);
        }
        Console.WriteLine(sum);
    }
}

关于 Span<T> 的更多信息请参考 Span<T> 结构 , 关于 fixed 的更多信息请参考 fixed 语句

增强的泛型约束

现在,可以将类型 System.EnumSystem.Delegate 指定为类型参数的基类约束。

  1. 泛型类型约束

    csharp
    public class UsingEnum<T> where T : System.Enum { }
    
    public class UsingDelegate<T> where T : System.Delegate { }
    
    public class Multicaster<T> where T : System.MulticastDelegate { }
  2. 形参约束 - 枚举(Enum)

    csharp
    public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
    {
        var result = new Dictionary<int, string>();
        var values = Enum.GetValues(typeof(T));
    
        foreach (int item in values)
            result.Add(item, Enum.GetName(typeof(T), item));
        return result;
    }
  3. 形参约束 - 委托(Delegate)

    csharp
    public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
        where TDelegate : System.Delegate
        => Delegate.Combine(source, target) as TDelegate;
    
    Action first = () => Console.WriteLine("this");
    Action second = () => Console.WriteLine("that");
    
    var combined = first.TypeSafeCombine(second);
    combined();

现在也可以使用新的 unmanaged 约束来指定类型参数必须为 “非托管类型”

csharp
unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

提升了现有功能

元组支持 ==!=

这些运算符按顺序将左边参数的每个成员与右边参数的每个成员进行比较。只要有一对不相等,它们即会停止比较。

csharp
var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // displays 'true'

如果其中一个元组是可空元组,则元组相等将执行 提升转换

csharp
var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
(int a, int b)? nullableTuple = right;
Console.WriteLine(left == nullableTuple); // Also true

元组相等还将对这两个元组的每个成员执行 隐式转换 。这些转换包括 提升转换扩大转换其他隐式转换

csharp
// lifted conversions
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true

// converted type of left is (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Also true

// comparisons performed on (long, long) tuples
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Also true

元组成员名称不参与相等测试。 但是,如果其中一个操作数是含有显式名称的元组文本,则当这些名称与其他操作数的名称不匹配时,编译器将生成警告 CS8383

csharp
(int a, string b) pair = (1, "Hello");
(int z, string y) another = (1, "Hello");
Console.WriteLine(pair == another); // true. Member names don't participate.
Console.WriteLine(pair == (z: 1, y: "Hello")); // Also true, but have a warning: literal contains different member names

上述代码编译会有如下警告,但是比较的结果仍然是 true

由于元组 ==!= 运算符的另一侧指定了其他名称或未指定名称,因此元组元素名称“y”被忽略。
由于元组 ==!= 运算符的另一侧指定了其他名称或未指定名称,因此元组元素名称“z”被忽略。

将特性添加到自动实现的属性的支持字段

现在支持此语法:

csharp
[field: SomeThingAboutFieldAttribute]
public int SomeProperty { get; set; }

也就是通过 field 关键字指定特性到 自动生成的支持字段

对比下 [field: JsonIgnore][JsonIgnore] 两种写法的 IL 代码可以看出来:使用 field 关键字时,JsonIgnoreAttribute 会被加载 <SomeProperty>k__BackingField 字段上,而不是 SomeProperty 属性上。

下面分别是修改前和修改后的 C# 代码和对应的 IL 代码。

csharp
class SomeClass
{
    [field: JsonIgnore]
    public string SomeProperty { get; set; }
}
csharp
.class /*02000004*/ private auto ansi beforefieldinit SpanSample.SomeClass
       extends [System.Runtime/*23000001*/]System.Object/*01000012*/
{
  .field /*04000004*/ private string '<SomeProperty>k__BackingField'
  .custom /*0C000016:0A00000D*/ instance void [System.Runtime/*23000001*/]System.Runtime.CompilerServices.CompilerGeneratedAttribute/*0100000F*/::.ctor() /* 0A00000D */ = ( 01 00 00 00 ) 
  .custom /*0C000017:0A00000E*/ instance void [System.Diagnostics.Debug/*23000002*/]System.Diagnostics.DebuggerBrowsableAttribute/*01000011*/::.ctor(valuetype [System.Diagnostics.Debug/*23000002*/]System.Diagnostics.DebuggerBrowsableState/*01000010*/) /* 0A00000E */ = ( 01 00 00 00 00 00 00 00 ) 
  .custom /*0C000018:0A00000F*/ instance void [Newtonsoft.Json/*23000003*/]Newtonsoft.Json.JsonIgnoreAttribute/*01000013*/::.ctor() /* 0A00000F */ = ( 01 00 00 00 ) 
  .method /*06000007*/ public hidebysig specialname 
          instance string  get_SomeProperty() cil managed
  // SIG: 20 00 0E
  {
    .custom /*0C000019:0A00000D*/ instance void [System.Runtime/*23000001*/]System.Runtime.CompilerServices.CompilerGeneratedAttribute/*0100000F*/::.ctor() /* 0A00000D */ = ( 01 00 00 00 ) 
    // 方法在 RVA 0x20a8 处开始
    // 代码大小       7 (0x7)
    .maxstack  8
    IL_0000:  /* 02   |                  */ ldarg.0
    IL_0001:  /* 7B   | (04)000004       */ ldfld      string SpanSample.SomeClass/*02000004*/::'<SomeProperty>k__BackingField' /* 04000004 */
    IL_0006:  /* 2A   |                  */ ret
  } // end of method SomeClass::get_SomeProperty

  .method /*06000008*/ public hidebysig specialname 
          instance void  set_SomeProperty(string 'value') cil managed
  // SIG: 20 01 01 0E
  {
    .custom /*0C00001A:0A00000D*/ instance void [System.Runtime/*23000001*/]System.Runtime.CompilerServices.CompilerGeneratedAttribute/*0100000F*/::.ctor() /* 0A00000D */ = ( 01 00 00 00 ) 
    // 方法在 RVA 0x20b0 处开始
    // 代码大小       8 (0x8)
    .maxstack  8
    IL_0000:  /* 02   |                  */ ldarg.0
    IL_0001:  /* 03   |                  */ ldarg.1
    IL_0002:  /* 7D   | (04)000004       */ stfld      string SpanSample.SomeClass/*02000004*/::'<SomeProperty>k__BackingField' /* 04000004 */
    IL_0007:  /* 2A   |                  */ ret
  } // end of method SomeClass::set_SomeProperty

  .method /*06000009*/ public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  // SIG: 20 00 01
  {
    // 方法在 RVA 0x20b9 处开始
    // 代码大小       8 (0x8)
    .maxstack  8
    IL_0000:  /* 02   |                  */ ldarg.0
    IL_0001:  /* 28   | (0A)000012       */ call       instance void [System.Runtime/*23000001*/]System.Object/*01000012*/::.ctor() /* 0A000012 */
    IL_0006:  /* 00   |                  */ nop
    IL_0007:  /* 2A   |                  */ ret
  } // end of method SomeClass::.ctor

  .property /*17000004*/ instance string SomeProperty()
  {
    .get instance string SpanSample.SomeClass/*02000004*/::get_SomeProperty() /* 06000007 */
    .set instance void SpanSample.SomeClass/*02000004*/::set_SomeProperty(string) /* 06000008 */
  } // end of property SomeClass::SomeProperty
} // end of class SpanSample.SomeClass
csharp
class SomeClass
{
    [JsonIgnore]
    public string SomeProperty { get; set; }
}
csharp
.class /*02000004*/ private auto ansi beforefieldinit SpanSample.SomeClass
       extends [System.Runtime/*23000001*/]System.Object/*01000012*/
{
  .field /*04000004*/ private string '<SomeProperty>k__BackingField'
  .custom /*0C000016:0A00000D*/ instance void [System.Runtime/*23000001*/]System.Runtime.CompilerServices.CompilerGeneratedAttribute/*0100000F*/::.ctor() /* 0A00000D */ = ( 01 00 00 00 ) 
  .custom /*0C000017:0A00000E*/ instance void [System.Diagnostics.Debug/*23000002*/]System.Diagnostics.DebuggerBrowsableAttribute/*01000011*/::.ctor(valuetype [System.Diagnostics.Debug/*23000002*/]System.Diagnostics.DebuggerBrowsableState/*01000010*/) /* 0A00000E */ = ( 01 00 00 00 00 00 00 00 ) 
  .method /*06000007*/ public hidebysig specialname 
          instance string  get_SomeProperty() cil managed
  // SIG: 20 00 0E
  {
    .custom /*0C000019:0A00000D*/ instance void [System.Runtime/*23000001*/]System.Runtime.CompilerServices.CompilerGeneratedAttribute/*0100000F*/::.ctor() /* 0A00000D */ = ( 01 00 00 00 ) 
    // 方法在 RVA 0x20a8 处开始
    // 代码大小       7 (0x7)
    .maxstack  8
    IL_0000:  /* 02   |                  */ ldarg.0
    IL_0001:  /* 7B   | (04)000004       */ ldfld      string SpanSample.SomeClass/*02000004*/::'<SomeProperty>k__BackingField' /* 04000004 */
    IL_0006:  /* 2A   |                  */ ret
  } // end of method SomeClass::get_SomeProperty

  .method /*06000008*/ public hidebysig specialname 
          instance void  set_SomeProperty(string 'value') cil managed
  // SIG: 20 01 01 0E
  {
    .custom /*0C00001A:0A00000D*/ instance void [System.Runtime/*23000001*/]System.Runtime.CompilerServices.CompilerGeneratedAttribute/*0100000F*/::.ctor() /* 0A00000D */ = ( 01 00 00 00 ) 
    // 方法在 RVA 0x20b0 处开始
    // 代码大小       8 (0x8)
    .maxstack  8
    IL_0000:  /* 02   |                  */ ldarg.0
    IL_0001:  /* 03   |                  */ ldarg.1
    IL_0002:  /* 7D   | (04)000004       */ stfld      string SpanSample.SomeClass/*02000004*/::'<SomeProperty>k__BackingField' /* 04000004 */
    IL_0007:  /* 2A   |                  */ ret
  } // end of method SomeClass::set_SomeProperty

  .method /*06000009*/ public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  // SIG: 20 00 01
  {
    // 方法在 RVA 0x20b9 处开始
    // 代码大小       8 (0x8)
    .maxstack  8
    IL_0000:  /* 02   |                  */ ldarg.0
    IL_0001:  /* 28   | (0A)000012       */ call       instance void [System.Runtime/*23000001*/]System.Object/*01000012*/::.ctor() /* 0A000012 */
    IL_0006:  /* 00   |                  */ nop
    IL_0007:  /* 2A   |                  */ ret
  } // end of method SomeClass::.ctor

  .property /*17000004*/ instance string SomeProperty()
  {
    .custom /*0C000018:0A00000F*/ instance void [Newtonsoft.Json/*23000003*/]Newtonsoft.Json.JsonIgnoreAttribute/*01000013*/::.ctor() /* 0A00000F */ = ( 01 00 00 00 ) 
    .get instance string SpanSample.SomeClass/*02000004*/::get_SomeProperty() /* 06000007 */
    .set instance void SpanSample.SomeClass/*02000004*/::set_SomeProperty(string) /* 06000008 */
  } // end of property SomeClass::SomeProperty
} // end of class SpanSample.SomeClass

in 方法重载解析决胜属性

这个应该是之前在 7.2 中使用下面的写法时,会导致重载的多义性。在 7.3 中修复了这个问题,另外在 7.2 中也作为 bug 修复了。也就是说不会再有这个多义性的问题了。

csharp
static void M(S arg);
static void M(in S arg);

扩展初始值设定项中的表达式变量

已对在 C# 7.0 中添加的允许 out 变量声明的语法进行了扩展,以包含字段初始值设定项、属性初始值设定项、构造函数初始值设定项和查询子句。它允许使用如以下示例中所示的代码:

csharp
public class B
{
   public B(int i, out int j)
   {
      j = i;
   }
}

public class D : B
{
   public D(int i) : base(i, out var j)
   {
      Console.WriteLine($"The value of 'j' is {j}");
   }
}

没啥好说的,如上所述就是 C# 7.0 out 变量声明语法的增强。

改进了重载候选项

添加了 3 个新的重载候选规则。不太理解这个影响到哪里,感觉应该只是影响编写代码时重载的候选列表。具体规则看 这里

新的编译器选项

公共或开放源代码签名

-publicsign 编译器选项指示编译器使用公钥对程序集进行签名。程序集被标记为已签名,但签名取自公钥。此选项使你能够使用公钥在开放源代码项目中构建签名的程序集。详细信息参阅 [MSDN] 。

使用 -publicsign 选项时需要配合 -keyfile-keycontainer 选项。使用 sn -k filename 命令可以生成 -keyfile 选项所需要的文件。示例如下:

bash
sn -k publickey
csc -publicsign -keyfile:publickey Program.cs

这个三个选项的详细信息请参阅 -publicsign(C# 编译器选项)-keyfile(C# 编译器选项)-keycontainer(C# 编译器选项)

pathmap

-pathmap 编译器选项指示编译器将生成环境中的源路径替换为映射的源路径。 -pathmap 选项控制由编译器编写入 PDB 文件或为 CallerFilePathAttribute 编写的源路径。

bash
csc -pathmap:C:\Users\liujiajia\source\repos=D:\repos Program.cs

下面是一个替换 CallerFilePathAttribute 特性源路径的示例。

csharp
using System;

namespace CallerFilePathSample
{
    class Program
    {
        static void Main(string[] args)
        {
            TraceMessage("Something happened.");
        }

        static void TraceMessage(string message,
                [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
                [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
                [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
        {
            Console.WriteLine("message: " + message);
            Console.WriteLine("member name: " + memberName);
            Console.WriteLine("source file path: " + sourceFilePath);
            Console.WriteLine("source line number: " + sourceLineNumber);
        }
    }
}

正常运行结果如下:

message: Something happened.
member name: Main
source file path: C:\Users\liujiajia\source\repos\CallerFilePathSample\CallerFilePathSample\Program.cs
source line number: 9

下面通过 -pathmap 选项重新编译。

bash
C:\Users\liujiajia\source\repos\CallerFilePathSample\CallerFilePathSample>csc -pathmap:C:\Users\liujiajia\source\repos=D:\repos Program.cs
Microsoft(R) Visual C# 编译器 版本 3.3.1-beta3-19461-02 (2fd12c21)
版权所有(C) Microsoft Corporation。保留所有权利。

重新之后结果如下:

bash
C:\Users\liujiajia\source\repos\CallerFilePathSample\CallerFilePathSample>Program.exe
message: Something happened.
member name: Main
source file path: D:\repos\CallerFilePathSample\CallerFilePathSample\Program.cs
source line number: 9

可以看到 source file path 的值已经变掉了。