C Sharp语法

C Sharp是编写该语言程序的一套规则。也适用于.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使用具化生成泛型参数,它允许优化和保存类型信息。

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

public class Node 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 GetEven(IEnumerable numbers)
{
foreach(int i in numbers)
{
if (i % 2 == 0) yield return i;
}
}

注意事项:

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

匿名方法
匿名方法类似于函数式编程中的闭包。匿名方法是通过使用 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 == ...)
{
// ...
}

// ...
});
}

委托的协变和逆变
委托签名的协变和逆变,

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

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的一部分,它的新特性灵感来自于函数式编程语言,如:Haskell和ML,并广泛地引入了Language Integrated Query(LINQ)模式到通用語言運行庫中e.

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

类型初始化器
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

匿名類型
var x = new { Name="James" };

局部变量类型推断
局部变量类型推断:var x = new Dictionary>();
等同于
Dictionary> x = new Dictionary>();
它只是一个语法糖,这个特性被匿名类型声明时所需要

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();

注意事项:

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

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

分部方法(Partial methods)必须定义在分部类(partial classes)中

定义分部方法需要用partial做修饰符

分部方法不一定总是有执行内容的,也就是说定义的方法可以一句操作语句都没有

分部方法返回值必须是void

分部方法可以是静态(static)方法

分部方法可以包含参数,参数可以包含以下修饰词:this,ref,params

分部方法必须是私有(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, Python或Ruby。

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-{zh:修饰字;zh-hk:修饰字;zh-cn:关键字}-。

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

public interface IEnumerable : IEnumerable
{
IEnumerator GetEnumerator();
}

C# 5.0的特性
Async
async和await是一对语法糖,允许开发人员非常轻松的调用基于TASK的异步编程。async-await关键字并不会真的创建一个线程池任务,完成这个动作依赖于被调用方法中的函数。

Caller info attributes
CallerInfoAttributes用于调试时访问调用者的信息。包括三个主要的类:

  • [CallerMemberName] :返回调用函数的名称。
  • [CallerFilePath] :返回调用函数所在源文件全路径信息 。
  • [CallerLineNumber] :返回调用函数调用具体行号。

using System;
using System.Runtime.CompilerServices;

namespace TestPro
{
class Program
{
public static void Main()
{
Log("Test.");
}

// 对日志消息进行记录,同时所有内容均有默认值,如果获取失败,则使用默认值。
public static void Log(string message,
[CallerMemberName] string callerName = "unknown",
[CallerFilePath] string callerFilePath = "unknown",
[CallerLineNumber] int callerLineNumber = -1)
{
Console.WriteLine("Message: {0}", message);
Console.WriteLine("Caller's Name: {0}", callerName);
Console.WriteLine("Caller's FilePath: {0}", callerFilePath);
Console.WriteLine("Caller's LineNumber: {0}", callerLineNumber);
}
}
}
/*
程序执行以后会显示以下内容。
Message: Test.
Caller Name: Main
Caller FilePath: C:UsersAdministratorsource
eposTestProProgram.cs
Caller Line number: 10
请按任意键继续. . .*/

绑定运算符
:=: 绑定运算符简化了数据绑定
comboBox1.Text :=: textBox1.Text; //将文本框的内容绑定到下拉框。
带参数的泛型构造函数
这个的加入给一些设计增加了强大功能,泛型早在C#2.0加入后就有着强大的应用,一般稍微设计比较好的框架,都会用到泛型,C#5.0加入带参数泛型构造函数,则在原有基础上对C#泛型完善了很多。

class MyClass
where T : class, new ()
{
public MyClass()
{
this.MyObject = new T();
}

T MyObject { get; set; }
}

case支持表达式
以前case里只能写一个具体的常量,而现在可以加表达式

switch(myobj){
case string.IsNullorEmpty(myotherobj):
.....
case myotherobj.Trim().Lower:
....
}

扩展属性
[Associate(string)]
public static int MyExtensionProperty { get;set;}

支持null类型运算
int x? = null;
int y? = x + 40;

Myobject obj = null;
Myotherobj obj2 = obj.MyProperty ??? new Myotherobj();

C# 6.0的特性
只读自动属性
不必使用完整属性语法定义属性。可以在声明属性的位置或类型的构造函数中初始化属性。

class Person
{
//新语法
private string Name { get; } = "Fanguzai"; //不用带上 private set;

//旧语法
public int Age { get; private set; } ;
}

自动屬性初始化
class MyClass
{
public string Name { get; set; } = "Fanguzai";
}

具有表达式体的函数成员
可以采用与用于 lambda 表达式相同的轻量语法,声明代码为单个表达式的成员。具有立即仅返回表达式结果,或单个语句作为方法主体。C# 6.0支持方法和属性get。C# 7.0扩展支持了构造函数、终结器、属性set、索引器。

using System;

public class Person
{
public Person(string firstName, string lastName)
{
fname = firstName;
lname = lastName;
}

private string fname;
private string lname;

public override string ToString() => $"{fname} {lname}".Trim(); //返回值类型string
public void DisplayName() => Console.WriteLine(ToString()); //返回值类型void
public string Name => $"{fname} {lname}".Trim();//只读属性
}

使用靜態类
可以导入静态类型的可访问静态成员,以便可以在不使用类型名限定访问的情况下引用成员。

using System;
using static System.Console;

namespace _usingStatic
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hi,Fanguzai!");
WriteLine("Hi,Fanguzai!"); // 使用了 using static System.Console;
}
}
}

nameof 運算符
class Program
{

private static void Func1(int x) { }
private string F() => nameof(T);
private void Func2(string msg) { }

static void Main(string[] args)
{
var program = new Program();

Console.WriteLine(nameof(System.Text));
Console.WriteLine(nameof(Func1));
Console.WriteLine(nameof(Program));
Console.WriteLine(nameof(program));
Console.WriteLine(nameof(F));

Console.Read();
}
}

内插字符串
以$为前缀构造字符串。 内插字符串表达式类似于包含表达式的模板字符串。内插字符串表达式通过将包含的表达式替换为表达式结果的ToString表现形式来创建字符串。

var name = "Fanguzai";
Console.WriteLine($"Hello, {name}");

想要在内插字符串中包含大括号“{” 或 “}”,使用两个大括号,即 “”。

NULL 条件运算符
用于在执行成员访问 ?. 或索引 ?[ 操作之前,测试是否存在 NULL 值。

static void Main(string[] args)
{
string name = null;
Console.WriteLine($"1:{name?.Length}");

name = "Fanguzai";
Console.WriteLine($"2:{name?.Length}");
Console.WriteLine($"3: {name?[0]}");
}

//普通的委托调用
Func func = () => 0;
if (func!=null)
{
func();
}

//简化调用
func?.Invoke();

Catch 和 Finally 區塊中的 Await
async Task Test()
{
var wc = new WebClient();
try
{
await wc.DownloadDataTaskAsync("");
}
catch (Exception)
{
await wc.DownloadDataTaskAsync("");  //OK
}
finally
{
await wc.DownloadDataTaskAsync("");  //OK
}
}

索引初始設定
var nums = new Dictionary
{
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};

//这是旧的方式
var otherNums = new Dictionary()
{
{1, "one"},
{2, "two"},
{3, "three"}
};

异常过滤器 (Exception filters)
try
{
var numbers = new Dictionary {[7] = "seven",[9] = "nine",[13] = "thirteen" };
}
catch (ArgumentNullException e)
{
if (e.ParamName == "customer")
{
Console.WriteLine("customer can not be null");
}
}

集合初始設定式的擴充方法
改進的多載解析
C# 7.0的特性
具有表达式体的函数成员
可以采用与用于 lambda 表达式相同的轻量语法,声明代码为单个表达式的成员。具有立即仅返回表达式结果,或单个语句作为方法主体。C# 6.0支持方法和属性get。C# 7.0扩展支持了构造函数、终结器、属性set、索引器。

class MyClass
{
public string this[int i] //索引器
{
get => types[i];
set => types[i] = value;
}

private string[] types = { "Baseball", "Basketball", "Football","Hockey", "Soccer", "Tennis","Volleyball" };

public MyClass(string name) => Name = name; //构造函数
private string locationName;

~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");//终结器

public string Name => locationName; //属性get

public string Name{
get => locationName;
set => locationName = value; //属性set
}
}

using System;
public class Location
{
private string locationName;

public Location(string name) => Name = name; //构造函数

public string Name
{
get => locationName; //get属性
set => locationName = value; //set属性
}

public override string ToString() => GetType().Name;

~Location() => Console.WriteLine($"The {ToString()} finalizer is executing."); //析构函数

private string[] types = { "Baseball", "Basketball", "Football",
"Hockey", "Soccer", "Tennis",
"Volleyball" };

public string this[int i]
{
get => types[i]; //索引器
set => types[i] = value;
}
}

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

static void AssignVal(out string strName)
{
strName = "I am from OUT";
}
static void Main(string[] args)
{
AssignVal(out var szArgu); //无需提前声明该变量
}

弃元
元组/对象的解构:
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));
}
元组
( string, string, string, string) getEmpInfo()
{
//read EmpInfo from database or any other source and just return them
string strFirstName = "abc";
string strAddress = "Address";
string strCity= "City";
string strState= "State";
return (strFirstName, strAddress, strCity, strState); // tuple literal
}

//Just call above method and it will return multiple values
var empInfo= getEmpInfo();
Console.WriteLine($"Emp info as {empInfo .Item1} {empInfo .Item2} {empInfo .Item3} {empInfo .Item4}.");

//也可以给元组中各项命名:
(string strFName, string strAdd, string strC, string strSt) getEmpInfo_named()
{
//code goes here
//直接在元组文字中返回其名称,如下所示
return (strFName: strFirstName, strAdd: strAddress, strCity: strC, strState: strSt);
}
//Now when you call method get values with specific name as below
var empInfo1= getEmpInfo();
Console.WriteLine($"Emp info as {empInfo1.strFName} {empInfo1.strAdd} {empInfo1.strC} {empInfo1.strSt}.");

.NET有Tuple类型,但是它是一个引用类型,会导致性能问题,但是C#7.0带来了具有值类型的Tuple,该类型具有更快的性能和可变的类型。

;元组的解构
大多数时候,不想访问整个元组包,或者只需要内部值,那么可以使用元组解构并获取所需的值

( string strFName, string strAdd, string strC, string strSt) = getEmpInfo();
Console.WriteLine($"Address: { strAdd }, Country: { strC }");

记录类型
记录类型只不过是属性和变量的容器。借助记录类型可以减少工作量。

//一个带有属性,构造函数和变量的类,因此访问和声明变量需要编写更多代码:
class studentInfo
{
string _strFName;
string _strMName;
string _strLName;
studentInfo(string strFN, string strMN, string strLN){
this._strFName = strFN;
this._strMName = strMN;
this._strLName = strLN;
}
public string StudentFName {get{ return this._strFName;}}
public string StudentMName {get{ return this._strMName;}}
public string StudentLName {get{ return this._strLName;}}
}

//具有相同功能的记录类型:
class studentInfo(string StudentFName, string StudentMName, string StudentLName);

不可为空的引用类型
int objNullVal; //non-nullable value type
int? objNotNullVal; //nullable value type
string! objNotNullRef; //non-nullable reference type 不可为空的引用类型
string objNullRef; //nullable reference type

本地方法
可以在其中使用out或ref。

private static void Main(string[] args)
{
int local_var = 100;
int LocalFunction(int arg1)
{
return local_var * arg1;
}
Console.WriteLine(LocalFunction(100));
}

数值字面量中包含下划线分隔符以提高可读性
static void Main(string[] args)
{
var lit1 = 478_1254_3698_44;
var lit2 = 0Xab_bc47;
//C# also come with binary literal for bunary values
var binLit = 0b1_00;
}

模式匹配
允许在IS语句和SWITCH语句中把模式与任何数据类型匹配。模式可以是常量模式、类型模式、变量模式。

public void Method1( object obj)
{
//following null is constant pattern
if (obj is null) return;
//datatype pattern, string is datatype that we check directly
if (obj is string st)
{ //code goes here }
else
return;
}

class Calculate();
class Add(int a, int b, int c) : Calculate;
class Substract(int a, int b) : Calculate;
class Multiply(int a, int b, int c) : Calculate;

Calculate objCal = new Multiply(2, 3, 4);
switch (objCal)
{
case Add(int a, int b, int c):
//code goes here
break;
case Substract(int a, int b):
//code goes here
break;
case Multiply(int a, int b, int c):
//code goes here
break;
default:
//default case
break;
}

变量作为Ref从方法返回
通过Ref传递一个变量,将它作为Ref返回,并将它们存储为Ref

class Program
{
static string emptyString;
static ref string getFromList(string strVal, string[] Values)
{
var kk = Values.GetLength(0);
for (int i = 0; i
在上面的示例中,我们通过从方法返回Ref找到并替换了字符串。
从表达式中抛出异常
public string getEmpInfo( string EmpName)
{
string[] empArr = EmpName.Split(",");
return (empArr.Length > 0) ? empArr[0] : throw new Exception("Emp Info Not exist");
}

C# 7.1的特性
async Main方法
static async Task Main()//注意返回Task
{
await AsyncMethod1();
}

default常值運算式
Func 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

數值常值中的前置下划线分隔符
// 在C# 7.2中,_可以用在0x0b之后
0x_1_2 // C# 7.2及更高版本可用
0b_1_0_1 // C# 7.2及更高版本可用

私有保护访问修饰符
只有当前项目里的继承关系才能用,出了这个程序集就没办法用了,即 private protected 为私有继承(protected 且 internal)。
C# 7.3的特性
改进stackalloc运算符
int* Array1 = stackalloc int[3] {5, 6, 7};//老语法

Span 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(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的特性

递归的模式匹配

在编译器可做类型推断的情况下,允许进一步省略类型声明

可释放的引用结构
声明为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 lstNum = null;
int? a = null;

(lstNum ??= new List()).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。

Span 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枚举器返回异步流。此方法声明为anync修饰符,此函数还包含yield return 语句用于返回异步流。为了消费anync流,我们需要在实际返回值之前使用await关键字。

public async System.Collections.Generic.IAsyncEnumerable asyncStream()
{
for (int iCount = 0; iCount
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類型,並且屬性StudentName與StudentID只能在初始化時賦予其值。

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之後,最上層语句(top-level statement)的程式碼不需要包含namespace以及class,可將其簡化為:

using System;

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

注意, 一个程序中, 只能有一个文件使用顶级语句, 并且顶级语句必须位于命名空间或类型定义之前!
lambda弃元参数
Func zero = (_, _) => 0;
Func 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* 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 dict = new();

Point[] points = { new(1, 1), new (2, 2), new (3, 3) };
var list = new List { 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 GetEnumerator(this IEnumerator enumerator) => enumerator;
}

class Program
{
static void Main(string[] args)
{
IEnumerator enumerator = new Collection {"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
{
(_, "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 {
(_, "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 {
(_, "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 "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
结构的无参数构造函数
无参数的结构体构造函数(Parameterless constructors in structs)。无参构造函数使得new struct() 和 default(struct) 的语义不一样。

struct Person
{
public string Name { get; }
public int Age { get; }
public Person(string a, int b) { Name = a; Age = b; }
public Person() : this("Joe", 40) {}
}

用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
var g = string (int x, string y) => $"{y}{x}"; // Func
var h = "test".GetHashCode; // Func
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
包括强类型的代码构建器,以及增量编译的支持等

参见
*.NET Framework
*C Sharp
*Mono

参考文献
#
#[http://bartdesmet.net/blogs/bart/archive/2005/08/09/3438.aspx Bart de Smet on Spec#]

外部链接
*[http://www.microsoft.com/net/ Microsoft .NET Framework]
*[http://www.mono-project.com/ Mono project]

评论 (0)

  • 还没有评论,来抢沙发吧。