跳转到内容

C Sharp语法

维基百科,自由的百科全书

这是本页的一个历史版本,由Paho.mqtt留言 | 贡献2022年6月28日 (二) 02:29编辑。这可能和当前版本存在着巨大的差异。

C Sharp语法英语Syntax (programming languages)是编写该语言程序的一套规则。也适用于.NET Framework and Mono

C# 2.0的特性

针对于.NET SDK 2.0(相对应于ECMA-334标准第三版),C# 的新特性有:

分部類別

分部類別将類別的实现分在多个文件中。该概念于C# 中首次出现,除了能将一个類別的成员分开存放,还使ASP.NET中的代码后置得以实现。代码后置实现了HTML代码和后台交互代码的分离。

file1.cs:

public partial class MyClass1
{
    public void MyMethod1()
    {
        // implementation
    }
}

file2.cs:

public partial class MyClass1
{
    public void MyMethod2()
    {
        // implementation
    }
}

分部類別这个特性允许将一个類別的编写工作分配给多个人,一人写一个文件,便于版本控制。它又可以隔离自动生成的代码和人工书写的代码,例如设计窗体应用程序时。

泛型

泛型,或参数化类型,是被C#支持的.NET 2.0特性。不同于C++模版,.NET参数化类型是在运行时被实例化,而不是编译时,因此它可以跨语言,而C++模版却不行。C#泛型类在编译时,先生成中间代码IL,通用类型符号T只是一个占位符;在实例化类时,根据实际数据类型代替T并由即时编译器(JIT)生成本地代码,其中使用了实际的数据类型,等同于用实际类型写的普通的类。

它支持的一些特性并不被C++模版直接支持,比如约束泛型参数实现一个接口。另一方面,C# 不支持无类型的泛型参数。不像Java中的泛型,在CLI虚拟机中,.NET generics使用具化生成泛型参数,它允许优化和保存类型信息。[1]

泛型类中,可以用where关键字对参数类型实现约束。例如:

 public class Node<T, V> where T : Stack, IComparable, new(),class where V: Stack,struct
 {...}

上述表示T和V必须是Stack类或其派生类,T必须继承了IComparable接口、有无参构造函数、是引用类型;V必须是值类型。

泛型不仅能作用在类上,也可单独用在类的方法上,称为“泛型方法”。

泛型类的静态成员变量在相同封闭类间共享,不同的封闭类间不共享。

泛型类中的方法重载,参数类型T和V在运行时确定,不影响这个类通过编译。C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。特别地,当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。

静态類別

静态類別它不能被实例化,并且只能有静态成员。这同很多过程语言中的模块概念相类似。

迭代器

一种新形式的迭代器它提供了函数式编程中的generator,使用yield return

类似于Python中使用的yield

// Method that takes an iterable input (possibly an array)
// and returns all even numbers.
public static IEnumerable<int> GetEven(IEnumerable<int> numbers)
{
    foreach(int i in numbers)
    {
        if (i % 2 == 0) yield return i;
    }
}

注意事项:

  • foreach循环时考虑线程安全性,不要试图对被遍历的集合进行remove和add等操作
  • IEnumerable接口是LINQ特性的核心接口。只有实现了IEnumerable接口的集合,才能执行相关的LINQ操作,比如select,where等

匿名方法

匿名方法类似于函数式编程中的闭包[2]匿名方法是通过使用 delegate 关键字创建委托实例来声明的。例如:


delegate void NumberChanger(int n);
 
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};



public void Foo(object parameter)
{
    // ...

    ThreadPool.QueueUserWorkItem(delegate
    {
        // anonymous delegates have full access to local variables of the enclosing method
        if(parameter == ...)
        { 
            // ... 
        }

        // ...
    });
}

委托的协变和逆变

委托签名的协变和逆变,[3]

属性访问器可以被单独设置访问级别

例子:

string status = string.Empty;

public string Status
{
    get { return status; }             // anyone can get value of this property,
    protected set { status = value; }  // but only derived classes can change it
}

可空类型

可空类型(跟个问号,如int? i = null;)允许设置null给任何类类型。

int? i = null;
object o = i;
if(o == null)
    Console.WriteLine("Correct behaviour - runtime version from September 2005 or later");
else
    Console.WriteLine("Incorrect behaviour - pre-release runtime (from before September 2005)");

??運算子

??):如果左运算数表达式的值不为空值时回傳该值,如果为空值则返回右运算数表达式的值。

object nullObj = null; 
object obj = new Object(); 
return nullObj ?? obj; // returns obj

主要用作将一个可空类型赋值给不可空类型的简便语法

int? i = null;
int j = i ?? 0; // Unless i is null, initialize j to i. Else (if i is null), initialize j to 0.

C# 3.0的特性

C# 3.0发布于2007年10月17日,是.NET Framework 3.5的一部分,它的新特性灵感来自于函数式编程语言,如:HaskellML,并广泛地引入了Language Integrated Query(LINQ)模式到通用語言運行庫中e.[4]

Linq

语言集成查询(英語:Language Integrated Query,缩写:LINQ):[5] 上下文相关关键字"from, where, select"可用于查询SQL、XML、集合等。这些标识符在LINQ上下文中被作为关键字,但是它们的增加不会破坏原有的名为fromwhereselect的变量。

类型初始化器

Customer c = new Customer();
c.Name = "James";

可写作:

Customer c = new Customer() { Name="James" };

集合初始化器

MyList list = new MyList();
list.Add(1);
list.Add(2);

可写作

MyList list = new MyList { 1, 2 };

假设MyList实现了System.Collections.IEnumerable且有一个Add方法method[6]

匿名類型

var x = new { Name="James" };

局部变量类型推断

局部变量类型推断

var x = new Dictionary<string, List<float>>();

等同于

Dictionary<string, List<float>> x = new Dictionary<string, List<float>>();

它只是一个语法糖,这个特性被匿名类型声明时所需要

Lambda表达式

Lambda表达式(無函式名稱的物件方法在程式語言中的表達語法):

listOfFoo.Where(
    delegate(Foo x)
    {
        return x.Size > 10; 
    }
)
可写作
listOfFoo.Where(x => x.Size > 10);

编译器翻译Lambda表达式为强类型委托或强类型表达式树

注意事项:

  • 如果只有一个参数,可以省略括号(),例如 item=>{Console.WriteLine("只有一个参数{0}的Lambda表达式",item); };
  • 如果只有一个返回值的语句,可以省略花括号{}、return关键字、分号,例如 item => {return item % 2 == 0;};改写成:item =>item %2 == 0;
  • Lambda表达式可以分配给Func,Action或Predicate委托。

自动化属性

编译器将自动生成私有变量和适当的getter(get访问器)和setter(set访问器),如:

public string Name
{
    get; 
    set; 
}

扩展方法

扩展方法能够使现有的类型添加方法,而无需创建心的派生类型、重新编译或以其它方式修改原始类型。

使用拓展方法,必须在一个非嵌套、非泛型的静态类中定义一个静态方法,方法第一个参数必须附加this关键字作为前缀,第一个参数不能有其它修饰符(如ref或者out),这个方法将被编译器添加到该this的类型中。

public static class IntExtensions
{
    public static void PrintPlusOne(this int x) 
    {
        Console.WriteLine(x + 1);
    }
}
 
int foo = 0;
foo.PrintPlusOne();

注意事项:

  • 扩展方法只会增加编译器的工作,但不会影响程序运行性能(用继承的方式为一个类型增加特性反而会影响性能)
  • 如果原来的类中有一个方法,跟扩展方法一样,那么扩展方法不会被调用,编译器也不会提示

分部方法

允许代码生成器生成方法声明作为扩展点,如果有人在另一个部分类实现了它才会被包含于原代码编译。[7]

  1. 分部方法(Partial methods)必须定义在分部类(partial classes)中
  2. 定义分部方法需要用partial做修饰符
  3. 分部方法不一定总是有执行内容的,也就是说定义的方法可以一句操作语句都没有
  4. 分部方法返回值必须是void
  5. 分部方法可以是静态(static)方法
  6. 分部方法可以包含参数,参数可以包含以下修饰词:this,ref,params
  7. 分部方法必须是私有(private)方法

例子:

partial class C
{
    static partial void M(int i); // defining declaration
}
partial class C
{
    static partial void M(int i)
    {
        dosomething();
    }
}

C# 4.0的特性

dynamic类型

C# 4.0新增dynamic关键字,提供動態編程(dynamic programming),把既有的靜態物件標記為動態物件,類似javascript, PythonRuby

dynamic关键字标记的实例被处理成一个特殊包装的object对象,取消了CLI的编译时类型检查,编译时被假定支持任何操作,但如果并不实际支持则运行时报错。

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

具名參數與可選參數

public StreamReader OpenFile(
    string path,
    int bufferSize = 1024)
{
...
}

呼叫OpenFile時,順序可以完全顛倒:

OpenFile(bufferSize: 4096, path: "foo.txt");

與COM组件互動

在C#中打開一個Word文件:

static void Main(string[] args) {
    Word.Application wordApplication = new   
       Word.Application() {Visible = true};     
    wordApplication.Documents.Open(@"C:\plant.docx",   
       ReadOnly: true);  
}

在C#中指定Excel的某一格文字:

excelObj.Cells[5, 5].Value = "This is sample text";

泛型的协变和逆变

C# 4.0支援协变和逆变,例如在泛型介面可以加上in、out修饰字。

  public interface IComparer<in T>  
  {  
    int Compare(T left, T right);  
  }

  public interface IEnumerable<out T> : IEnumerable
  {
    IEnumerator<T> GetEnumerator();
  }

C# 5.0的特性

  1. C# Evolution Matrix
  2. Async Feature (补充: async和await是一对语法糖,允许开发人员非常轻松的调用基于TASK的异步编程)async-await关键字并不会真的创建一个线程池任务,完成这个动作依赖于被调用方法中的函数。这一点在许多C#的中文教程中被忽略,导致许多学习的新手误以为await关键字会直接创建一个新的线程池任务。
  3. Caller Information

C# 6.0的特性

  1. 唯讀 Auto 屬性
  2. Auto 屬性初始設定式
  3. 具有運算式主體的函式成員:
  4. 使用靜態
  5. Null - 條件運算子
  6. 字串插值
  7. 例外狀況篩選條件
  8. nameof 運算式
  9. Catch 和 Finally 區塊中的 Await
  10. 索引初始設定式
  11. 集合初始設定式的擴充方法
  12. 改進的多載解析

C# 7.0的特性

out 變數

能夠直接宣告一個變數在它要傳入的地方,當成一個 out 的引數[8]

弃元

元组/对象的解构:

var tuple = (1, 2, 3, 4, 5);
(_, _, _, _, var fifth) = tuple;

使用 is/switch 的模式匹配:

var obj = CultureInfo.CurrentCulture.DateTimeFormat;
switch (obj)
{
 case IFormatProvider fmt:
   Console.WriteLine($"{fmt} object");
   break;
 case null:
   Console.Write("A null object reference");
   break;
 case object _:
   Console.WriteLine("Some object type without format information");
   break;
}

if (obj is object _)
{
 ...
}

对具有 out 参数的方法的调用:

var point = new Point(10, 10);
// 只要 x, 不关心 y
point.GetCoordinates(out int x, out _);

作用域内独立使用场景:

void Test(Dto dto)
{
   _ = dto ?? throw new ArgumentNullException(nameof(dto));
}

C# 7.1的特性

async Main方法

static async Task Main()//注意返回Task
{
    await AsyncMethod1();
}

default常值運算式

Func<int, string> whereClause = default;

推斷的 tuple 項目名稱

int count = 3;
string colors = "colors of the flag";
var tupleCol = (count,colors); // here syntax gets trimmed

C# 7.2的特性

有条件的“ref”

ref var finalVal = ref (value1 != null ? ref val1_arr[0] : ref val2_arr[0]);

无需名称的命名参数

如果参数的顺序正确,则无需为参数分配名称

//suppose I have written a function and used below named arguments
EmpDetails(EmpID: 3, firstName: "Manavya", City: "Daswel");
 
//here in C#7.2, I can write the above code line as
EmpDetails(EmpID: 3, "Manavya", City: "Daswel");
 
//if you observed that firstName: "Manavya" is replaced by only "manavya" 
// and it is the beauty of the C#7.2

數值常值中的前置下划线分隔符

int val_bi = 0b_0110_01;

私有保护访问修饰符

只有当前项目里的继承关系才能用,出了这个程序集就没办法用了,即 private protected 为私有继承(protected 且 internal)。

C# 7.3的特性

改进stackalloc运算符

int* Array1 = stackalloc int[3] {5, 6, 7};//老语法

Span<int> Array = stackalloc [] {10, 20, 30};//新语法

固定语句支持更多类型

固定语句是防止垃圾回收器清除可移动变量的语句,可以使用fixed关键字创建这些语句。从C#7.3开始,固定语句支持具有以下内容的任何类型:GetPinnableReference()方法,返回的ref T是固定的。

非托管约束

一个成员是一个非托管型,如果它是byte,short,sbyte,int,long,char,bool类型。可以使用非托管约束来显示类型参数必须是具有非指针的非托管类型。

    unsafe public static byte[] convert_to_byte<T>(this T argument) where T : unmanaged
    {
        var size = sizeof(T);
        var result = new Byte[size];
        return result1;
    }

还可以使用System.Enum和System.Delegate作为基类约束。

元组支持等于和不等于运算符

    var exp1 = (val1: 100, val2: 20);
    var exp2 = (val1: 100, val2: 20);
    exp1 == exp2; //it will return displays as 'true'

重载的“in”方法的更改

为“按引用”和“按值”方法命名相同的方法时,它会抛出歧义异常,为避免此错误,C#7.3使用了“in”方法:

    static void calculateArea(var1 arg);
    static void calculateArea(in var1 arg);

扩展参数边界

可以在构造函数初始值设定项、属性初始值设定项中定义out参数。

    //here, we have defined the out parameter in constructor
    public class parent
    {
       public parent(int Input, out int Output)
       {
          Output = Input;
       }
    }
     
    //now let's use the above class in the following class
    public class Child : parent
    {
       public Child(int i) : base(Input, out var Output)
       {
          //"The value of 'Output' is " + Output
       }
    }

多个编译器选项

  • 使用公钥签名程序集:添加参数作为-public签名。
  • 从构建环境替换路径:用较早的映射源路径替换构建中的源路径:-pathmap:path1=sourcePath1,path2=sourcePath2

C# 8.0的特性

  1. 递归的模式匹配
  2. 在编译器可做类型推断的情况下,允许进一步省略类型声明

可释放的引用结构

声明为ref 的struct 无权实现任何接口,因为无权实现IDisposable 接口。必须使我们需要使用可释放的ref结构来访问void Dispose()方法。也可以配置readonly ref结构。

表达式形式的Switch关键字

switch用于模式匹配的表达式及其语法有所变化。

    public enum RandomNum
    {
        One,
        Three,
        Five,
        Seven,
        Six,
    }

    public static string getRandomNum(RandomNum iNum) =>
        iNum switch //switch关键字在变量之后使用
        {
            RandomNum.One => return "1",
            RandomNum.Three => return "3",
            RandomNum.Five => return "5",
            RandomNum.Seven => return "7",
            RandomNum.Six => return "6",
            _              => throw new Exception("invalid number value"), //默认关键字由_(下划线)符号代替
        };

空值结合赋值运算符

??=空值结合赋值运算符(null-coalescing):

some_Value ??= some_Value2;

以代替啰嗦的写法:

some_Value = some_Value ?? some_Value2;
    List<int> lstNum = null;
    int? a = null;
     
    (lstNum ??= new List<int>()).Add(100);
    Console.WriteLine(string.Join(" ", lstNum)); 
     
    // the output would be : 100
     
    lstNum.Add(a ??= 0);
    Console.WriteLine(string.Join(" ", numbers));  // output: 100 0
    // the output would be : 100 0

局部静态函数

将局部函数设为static函数,可证明局部函数不包含内部封闭循环中的变量。

    int Add()
    {
        int A = 5;
        return Sum(A);
     
        static int Sum(int val) => val + 3;
    }

默认接口方法

允许在声明接口时为接口成员提供默认实现。 ===将结构成员设为只读 现在可以将struct成员设为只读,而不是将整个struct成员设置为只读。

    public readonly struct getSqure1 //老的实现
    {
        public int InputA { get; set; }
        public int InputB { get; set; }
        public int output => Math.Pow(A,B);
     
        public override string ToString() =>
            $"The answer is : {output} ";
    }

    public struct getSqure //新的实现
    {
        public int InputA { get; set; }
        public int InputB { get; set; }
        public readonly int output => Math.Pow(A,B);
     
        public override string ToString() =>
            $"The answer is : {output} ";
    }

插值字符串的增强

string以$开头,这样的string则称为插值string,可包含插值表达式:

    string szName = "ABC";
    int iCount = 15;
     
    // String interpolation:
    Console.WriteLine($"Hello, {szName}! You have {iCount} apples");//输出将为“ Hello, ABC! You have 15 apples”

在C#8中,$@""或者@$""表示可以通过@或$来开始string。在早期版本的C#中,@仅在$之后被允许。

stackalloc作为表达式

语法:stackalloc T[E] 其中T是非托管类型,E是类型int的表达式。stackalloc用作表达式,如果返回结果System.Span<T>。

    Span<int> num = stackalloc[] { 20, 30, 40 };
    var index = num.IndexOfAny(stackalloc[] { 20 });
    Console.WriteLine(index);  // output: 0

新型索引(Index )和区间(Range)类型

System.Index和System.Range用于从列表/数组中获取项。

^(脱字符)表示索引符号。通常,从数组中获取项目时,索引以开头0,但是当使用这种新类型时,其索引(indices)从结尾开始并向上。

..范围运算符获取其操作数的开始和结束。

    var simArray = new string[]
    {                 
        "one",         // 0      ^4
        "two",         // 1      ^3
        "three",       // 2      ^2
        "four",        // 3      ^1
    };

    Console.WriteLine("fetch using simple array " + simArray[1]);
    //above code gives output as "two"
    Console.WriteLine("fetch using indices caret operator " + simArray[^1]);
    //above code gives output as "four"

            var dd1 = simArray[..];
            //above code gives output as "one" to "four"
            var dd2 = simArray[..3];
            //above code gives output as "one" to "three"
            var dd3 = simArray[2..];
            //above code gives output as "three" to "four"

在上面的示例中,数组中有四个项;索引从0开始,以3结束;但是对于索引(indices)从^ 4()到^ 1。

using 关键字声明变量

using关键字声明变量,并在使用该变量的地方结束之前释放该变量:

    var ReadFile(string szpath)
    {
     using StreamReader StrFile = new StreamReader(szpath);
     string iCount;
     string szLine;
     while ((szLine = StrFile.ReadLine()) != null) 
      { 
      Console.WriteLine(szLine); 
      iCount++; 
      } 
     // 达到该方法的闭合括号,便会立即disposed该变量。
    }

    var ReadFile_OldStyle(string szpath)
    {
    using (StreamReader StrFile = new StreamReader(szpath))
     {
      string iCount;
      string szLine;
      while ((szLine = StrFile.ReadLine()) != null) 
      { 
      Console.WriteLine(szLine); 
      iCount++; 
      }     
     } //StrFile 对象在using 作用域结束后就被释放了
    }

消费异步流

异步流由一个方法执行,该方法使用IAsyncEnumerable<T>枚举器返回异步流。此方法声明为anync修饰符,此函数还包含yield return 语句用于返回异步流。为了消费anync流,我们需要在实际返回值之前使用await关键字。

    public async System.Collections.Generic.IAsyncEnumerable<int> asyncStream()
    {
        for (int iCount = 0; iCount < 10; iCount++)
        {
            await Task.Delay(10);
            yield return iCount;
        }
    }
    //call above method in mail method
    await foreach (var num in asyncStream())
    {
        Console.WriteLine(num);
    }

C# 9.0的特性

新的「Record」類型

记录类型, 是一种引用类型, 默认是不可变的。 记录类型的相等判断可以通过引用或者结构进行判断的。

  • 优点:记录类型是轻量级的不可变类型,可以减少大量的代码, 可以按照结构和引用进行比较;
  • 缺点:需要实例化大量的对象;
// 默认不可变的记录类型
public record Person(string Name, int Age);
// 可变记录类型
public record MutablePerson(string Name, int Age) {
 public string Name { get; set; } = Name;
 public int Age { get; set; } = Age;
}
 
var person1 = new Person("Alice", 40);
var person2 = new Person("Alice", 40);
 
Console.WriteLine(person1 == person2); // True 结构相同
Console.WriteLine(person1.Equals(person2)); // True 结构相同
Console.WriteLine(ReferenceEquals(person1, person2)); // False, 引用不同
 
// 改变默认的记录! --> 创建一个新的记录。
var person3 = person1 with { Age = 43 };
Console.WriteLine(person3 == person1); // False 结构不同
 
// 解构 (Destruct) 一个记录, 将记录的属性提取为本地变量
var (name, age) = person3;
 
var person4 = new MutablePerson("Alice", 40);
person4.Age = 43;
 
// 记录类型也可以被继承
public record Citizen(string Name, int Age, string Country) : Person(Name, Age);
var citizen = new Citizen("Alice", 40, "China");
Console.WriteLine(person1 == citizen); // False 类型不同;

「init」存取子

init存取子表示該屬性所屬類型僅能在建構函式(Constructor)中或是屬性初始化式子中賦予其值,如果嘗試在其他地方設定該屬性的值,在編譯時便會遭編譯器阻止。

範例如下: 在這個範例中,建立了一個Student類型,並且屬性StudentNameStudentID只能在初始化時賦予其值。

public class Student
{
	public string StudentName { get; init; } = "Default Name";
	public string StudentID { get; init; } = "00000000";
	public Student()
    {
		
	}
	public Student(string studentName,string studentID)
	{
		StudentName = studentName;
		StudentID = studentID;
	}
}

如果在此時撰寫以下程式碼:

Student DemoStudent = new Student();
DemoStudent.StudentName = "Test Name";

編譯器便會無法編譯並且擲回錯誤。

而如果要建立學生名稱為「Test Name」,學生ID為「0001」的學生,則需要寫成:

Student DemoStudent = new Student() //物件初始化運算式
{
    StudentName = "Test Name";
    StudentID = "0001"
};

或是

Student DemoStudent = new Student("Test Name","0001"); //藉由類型的建構式初始化StudentName以及StudentID。

最上層陳述式或称顶级语句

在以前的版本,開發者在撰寫最上層陳述式(如Program.cs)程式碼時,需要包含完整的namespace與class架構,因此如果要撰寫Hello World程式時,程式碼就會是:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

但是在C#9.0之後,最上層陳述式的程式碼不需要包含namespace以及class,可將其簡化為:

using System;

Console.WriteLine("Hello World!");
//或者简化为一行语句:
System.Console.WriteLine(Hello World!);

注意, 一个程序中, 只能有一个文件使用顶级语句, 并且顶级语句必须位于命名空间或类型定义之前!

lambda弃元参数

Func<int, int, int> zero = (_, _) => 0;
Func<int, int, int> func = delegate (int _, int _) { return 0; };

在 C# 9 之前,即便不使用的 Lambda 参数也需要给它命名。C# 9 支持弃元参数一方面简化了命名,另一方面也节省了内存分配。更重要的是它使得编程的意图更明确,让人一看就知道这个参数是不用的,增强了代码的可读性和可维护性。

只能初始化的设置器

Init only setters,只能通过对象初始化进行赋值的属性。

public class InitDemo {
 public string Start { get; init; }
 public string Stop { get; init; }
}
 
// initDemo.Start = "Now"; // Error
// initDemo.End = "Tomorrow"; // Error
 
var initDemo = new InitDemo {
 Start = "Now",
 Stop = "Tomorrow"
};

函数指针

使用 delegate* 可以声明函数指针。

unsafe class FunctionPointer {
 static int GetLength(string s) => s.Length;
 delegate*<string, int> functionPointer = &GetLength;
}
 
public void Test() {
 Console.WriteLine(functionPointer("test")); // 4;
}

跳过本地初始化

[System.Runtime.CompilerServices.SkipLocalsInit]
static unsafe void DemoLocalsInit() {
 int x;
 // 注意, x 没有初始化, 输出结果不确定;
 Console.WriteLine(*&x);
}

原生整数类型

两个新的整数类型 nint 和 nunit , 依赖宿主机以及编译设定。

协变返回类型

协变返回类型为重写方法的返回类型提供了灵活性。覆盖方法可以返回从被覆盖的基础方法的返回类型派生的类型。

class Person { public virtual Person GetPerson() { return new Person(); } }
class Student : Person { public override  Student GetPerson() { return new Student(); } }

模块初始化代码

ModuleInitializerAttribute 为组件 (assembly) 定义初始化代码, 当初始化/加载时执行, 可以类比类的静态构造函数, 但是是组件级别的。

  • 必须是静态的、无参数的、无返回值的方法;
  • 不能是范型方法,也不能包含在范型类中;
  • 不能是私有函数,必须是公开 (public) 或者内部 (internal) 的函数;

静态 lambda 表达式

static 修饰符添加到 lambda 表达式或匿名方法 。这将无法捕获局部变量或实例状态,从而防止意外捕获其他变量。

分部方法扩展

移除了分部方法的下述限制:

  • 必须具有 void 返回类型。
  • 不能具有 out 参数。
  • 不能具有任何可访问性(隐式 private )。

初始化表达式的简化

如果创建对象的类型已知时,可以在new表达式中省略该类型。

Point p = new(1, 1);
Dictionary<string, int> dict = new();
 
Point[] points = { new(1, 1), new (2, 2), new (3, 3) };
var list = new List<Point> { new(1, 1), new(2, 2), new(3, 3)};

在本地函数上添加标记

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace CoreApp2
{  
    class Program
    {
        static void Main(string[] args)
        {
            [Conditional("DEBUG")]
            static void DoSomething([NotNull] string test)
            {
                System.Console.WriteLine("Do it!");
            }
            DoSomething("Doing!");
        }
   }
}

GetEnumerator 扩展

可以为任意类型添加一个 GetEnumerator 扩展方法, 返回一个 IEnumerator 或者 IAsyncEnumerator 实例, 从而在 foreach 循环中使用。

using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace CoreApp2
{
    public static class Extensions
    {
        public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
    }

    class Program
    {
        static void Main(string[] args)
        {
            IEnumerator<string> enumerator = new Collection<string> {"A", "B", "C"}.GetEnumerator();
            foreach (var item in enumerator)
            {
                Console.WriteLine(item);
            }
        }
    }
}

模式匹配增强

Type patterns 类型匹配,判断一个变量的类型

object obj = new int();
var type = obj switch {
 string => "string",
 int => "int",
 _ => "obj"
};
Console.WriteLine(type); // int

Relational patterns 关系匹配:

class Person { 
        public string name; 
        public int age; 
        public Person(string a, int b) { name = a;age = b; }
        public void Deconstruct(out string a,out int b){a = name;b = age; }
    }
class Program
    {        
        static void Main(string[] args)
        {
            var person1 = new Person("Alice", 40);
            var inRange = person1 switch
            {
                (_, < 18) => "less than 18",
                (_, > 18) => "greater than 18",
                (_, 18) => "18 years old!"
            };
            Console.WriteLine(inRange); // greater than 18
         }
     }

Conjunctive and patterns 逻辑合取匹配:

// And pattern
var person1 = new Person("Alice", 40);
var ageInRange = person1 switch {
 (_, < 18) => "less than 18",
 ("Zhang Zhimin", _) and (_, >= 18) => "Alice is greater than 18"
};
Console.WriteLine(ageInRange); // Alice is greater than 18

Disjunctive or patterns 逻辑析取匹配:

// Or pattern
var person1 = new Person("Alice", 40);
var ageInRange = person1 switch {
 (_, < 18) => "less than 18",
 (_, 18) or (_, > 18) => "18 or greater"
};
Console.WriteLine(ageInRange); // 18 or greater

Negated not patterns 逻辑非匹配

// Not pattern
var person1 = new Person("Alice", 40);
var meOrNot = person1 switch {
 not ("Alice", 40) => "Not me!",
 _ => "Me :-)"
};
Console.WriteLine(meOrNot); // Me :-)

Parenthesized patterns 带括号的优先级匹配:

// Parenthesized patterns
var is10 = new IsNumber(true, 10);
var n10 = is10 switch {
 ((_, > 1 and < 5) and (_, > 5 and < 9)) or (_, 10) => "10",
 _ => "not 10"
};
Console.WriteLine(n10); // 10

C# 10.0的特性

record struct

解决了 record 只能给 class 而不能给 struct 用的问题:

record struct Point(int X, int Y);

sealed record ToString 方法

可以把 record 里的 ToString 方法标记成 sealed

struct 无参构造函数

无参构造函数使得new struct() 和 default(struct) 的语义不一样

用with创建新的匿名类型对象

   var x = new { A = 1, B = 2 };
   var y = x with { A = 3 };

这里 y.A 将会是 3 。

全局的 using

可以给整个项目启用 using,不需要每个文件都写一份。

文件范围的 namespace

以前写 namespace 还得带一层大括号。现在如果一个文件里只有一个 namespace 的话,直接在文件开头写: namespace MyNamespace;

常量字符串插值

   const string x = "hello";
   const string y = $"{x}, world!";

lambda的改进

lambda 可以带 attributes

   f = [Foo] (x) => x; // 给 lambda 设置
   f = [return: Foo] (x) => x; // 给 lambda 返回值设置
   f = ([Foo] x) => x; // 给 lambda 参数设置

指定返回值类型

此前 C# 的 lambda 返回值类型靠推导,C# 10允许在参数列表之前显式指定 lambda 返回值类型:

f = int () => 4;

支持 ref 、in 、out 等修饰

f = ref int (ref int x) => ref x; // 返回一个参数的引用

头等函数

函数可以隐式转换到 delegate,于是函数上升为头等函数(first function):

   void Foo() { Console.WriteLine("hello"); }
   var x = Foo;
   x(); // hello

自然委托类型

lambda 可自动创建自然委托类型,于是不再需要写出类型:

   var f = () => 1; // Func<int>
   var g = string (int x, string y) => $"{y}{x}"; // Func<int, string, string>
   var h = "test".GetHashCode; // Func<int>

CallerArgumentExpression

使用CallerArgumentExpression这个attribute,编译器会自动填充调用参数的表达式字符串,例如:

   void Foo(int value, [CallerArgumentExpression("value")] string? expression = null)
   {
       Console.WriteLine(expression + " = " + value);
   }

当你调用 Foo(4 + 5) 时,会输出 4 + 5 = 9。这对测试框架极其有用

tuple 的混合定义和使用

   int y = 0;
   (var x, y, var z) = (1, 2, 3);

于是 y 就变成 2 了,同时还创建了两个变量 x 和 z,分别是 1 和 3 。

接口支持抽象静态方法

.NET 6中这个特性为preview特性。

泛型 attribute

在方法上指定 AsyncMethodBuilder

在方法上用 [AsyncMethodBuilder(...)],来使用自己实现的 async method builder,代替自带的 Task 或者 ValueTask 的异步方法构造器。有助于实现零开销的异步方法。

line 指示器支持行列和范围

以前 #line 只能用来指定一个文件中的某一行,现在可以指定行列和范围:

   #line (startLine, startChar) - (endLine, endChar) charOffset "fileName"
    
   // 比如 #line (1, 1) - (2, 2) 3 "test.cs"

嵌套属性模式匹配改进

以前在匹配嵌套属性的时候需要这么写:

if (a is { X: { Y: { Z: 4 } } }) { ... }

现在只需要简单的:

if (a is { X.Y.Z: 4 }) { ... }

改进的字符串插值

实现接近零开销的字符串插值。

Source Generator v2

包括强类型的代码构建器,以及增量编译的支持等

参见

参考文献

  1. ^ An Introduction to C# Generics. [2020-09-25]. (原始内容存档于2019-09-24). 
  2. ^ Anonymous Methods (C#). [2008-10-24]. (原始内容存档于2008-04-17). 
  3. ^ Covariance and Contravariance in Delegates (C#). [2008-10-24]. (原始内容存档于2008-10-12). 
  4. ^ Tim Anderson. C# pulling ahead of Java - Lead architect paints rosy C# picture. Reg Developer. The Register. 2006-11-14 [2007-01-20]. (原始内容存档于2007-01-21). 
  5. ^ LINQ. Microsoft MSDN. 2007 [2007-08-13]. (原始内容存档于2007-01-16) (英语). 
  6. ^ The Mellow Musings of Dr. T : What is a collection?. [2008-10-24]. (原始内容存档于2008-12-18). 
  7. ^ Partial Methods. [2007-10-06]. (原始内容存档于2007-10-16). 
  8. ^ 一覽 C# 7.0 中的新功能. [2016-09-14]. (原始内容存档于2018-10-02). 
  1. Archer, Tom. Inside C#. Microsoft Press. 2001. ISBN 0-7356-1288-9. 
  2. Bart de Smet on Spec#页面存档备份,存于互联网档案馆

外部链接