此條目介紹的是微软開發的程序设计语言。关于音樂上的音名,请见「
C♯ (音名) 」。
C# 是微软 推出的一种基于.NET框架 和后来的.NET 的、面向对象 的高级编程语言 。C#衍伸自C和C++,继承了C和C++的强大功能,同时去掉了一些复杂特性,使其成为C语言家族中高效强大的编程语言。C#以.NET框架类库作为基础,拥有类似Visual Basic 的快速开发能力。C#由安德斯·海尔斯伯格 主持开发,微软在2000年发布了这种语言,希望借助这种语言来取代Java 。C#已经成为Ecma国际 和国际标准组织 的标准规范。
设计目标
ECMA标准列出的C#设计目标:
C#旨在设计成为一种「简单、现代、通用」,以及面向对象的程序设计语言
此种语言的实现,应提供对于以下软件工程要素的支持:强类型檢查、数组维度检查、未初始化的变量引用检测、自动垃圾收集 (Garbage Collection,指一种記憶體自動释放技术)。软件必须做到強大、持久,并具有较强的编程生产力。
此种语言为在分布式环境中的开发提供适用的组件开发应用。
为使程序员容易迁移到这种语言,源代码的可移植性十分重要,尤其是对于那些已熟悉C和C++的程序员而言。
对国际化的支持非常重要。
C#适合为独立和嵌入式的系统编写程序,从使用复杂操作系统的大型系统到特定应用的小型系统均适用。
歷史
原Borland 公司的首席研发设计师安德斯·海爾斯伯格 (Anders Hejlsberg)在微軟開發了Visual J++ 1.0,很快的Visual J++由1.1版本升級到6.0版。SUN 公司认为Visual J++ 违反了Java开发平台的中立性,对微软提出了诉讼。2000年6月26日微软在奥兰多举行的“职业开发人员技术大会”(PDC 2000)上,發表新的语言C#。C#语言取代了Visual J++,語言本身深受Visual Basic、Java、C和C++ 的影響。
版本
語言特性
相對於C 和C++ ,這個語言在許多方面進行了限制和增強:
指针 (Pointer)只能用於不安全模式之中。大多數對象訪問透過安全的引用实现,以避免無效的調用,並且有許多算法用於验证溢出,指针只能用於調用值类型,以及受垃圾收集 控制的托管對象。
對象不能被顯式釋放,代替為當不存在被引用時透過垃圾回收器回收。
只允許單一繼承 (single inheritance),但是一個類可以實現多個接口 (interfaces)。
C#比C++更加類型安全 。默认的安全轉換是隱含轉換 ,例如由短整型轉換為長整型和從衍生類轉換為基本類。而接口布尔型同整型,及枚舉型同整型不允許隱含轉換,非空指针 (透過引用相似對象)同用戶定義類型的隱含轉換字段被顯式的確定,不同於C++的複製構造函數。
数组声明語法不同("int[] a = new int[5]"而不是"int a[5]")。
枚舉 位於其所在的命名空間 中。
C#中沒有模版 (Template),但是在C# 2.0中引入了泛型 (Generic programming),並且支持一些C++模版不支持的特性。比如泛型參數中的類型約束。另一方面,表達式不能像C++模版中被用於類型參數。
屬性 支持,使用類似訪問成員的方式調用。
完整的反射 支持。
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使用具化 生成泛型参数,它允许优化和保存类型信息。[ 16]
泛型类中,可以用where关键字对参数类型实现约束。例如:
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等
匿名方法
匿名方法类似于函数式编程中的闭包 。[ 17] 匿名方法是通过使用 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 == ...)
{
// ...
}
// ...
});
}
委托的协变和逆变
委托 签名的协变和逆变 。[ 18]
属性访问器可以被单独设置访问级别
例子:
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.[ 19]
Linq
语言集成查询 (英語:L anguage In tegrated Q uery ,缩写:LINQ ):[ 20] 上下文相关关键字"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[ 21]
匿名類型
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 ();
注意事项:
扩展方法只会增加编译器的工作,但不会影响程序运行性能(用继承的方式为一个类型增加特性反而会影响性能)
如果原来的类中有一个方法,跟扩展方法一样,那么扩展方法不会被调用,编译器也不会提示
分部方法
允许代码生成器生成方法声明作为扩展点,如果有人在另一个部分类实现了它才会被包含于原代码编译。[ 22]
分部方法(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修饰字。
public interface IComparer < in T >
{
int Compare ( T left , T right );
}
public interface IEnumerable < out T > : IEnumerable
{
IEnumerator < T > GetEnumerator ();
}
C# 5.0的特性
C# Evolution Matrix
Async Feature (补充: async和await是一对语法糖,允许开发人员非常轻松的调用基于TASK的异步编程)async-await关键字并不会真的创建一个线程池任务,完成这个动作依赖于被调用方法中的函数。这一点在许多C#的中文教程中被忽略,导致许多学习的新手误以为await关键字会直接创建一个新的线程池任务。
Caller Information
C# 6.0的特性
唯讀 Auto 屬性
Auto 屬性初始設定式
使用靜態
Null - 條件運算子
字串插值
例外狀況篩選條件
nameof 運算式
Catch 和 Finally 區塊中的 Await
索引初始設定式
集合初始設定式的擴充方法
改進的多載解析
表达式主体(Expression-bodied)用于类的方法和只读属性
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 (); //只读属性
}
C# 7.0的特性
out 變數
能夠直接宣告一個變數在它要傳入的地方,當成一個 out 的引數[ 23]
弃元
元组/对象的解构:
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 ));
}
表达式主体(Expression-bodied)用于类的属性、构造器、终结器、索引器
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 ;
}
}
C# 7.1的特性
async``Main方法
default常值運算式
推斷的 tuple 項目名稱
C# 7.2的特性
具備實值型別的參考語意
無後置具名引數
數值常值中的前置底線
private protected 存取修飾詞
C# 8.0的特性
可空引用类型
await yield return可异步返回的迭代器
Index 索引类型和Range区间类型
允许在声明接口时为接口成员提供默认实现
递归的模式匹配
表达式形式的Switch关键字
在编译器可做类型推断的情况下,允许进一步省略类型声明
C# 9的特性
新的「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 Student ()
{
}
public Student ( string studentName , string studentID )
{
StudentName = studentName ;
StudentID = studentID ;
}
public string StudentName { get ; init ; } = "Default Name" ;
public string StudentID { get ; init ; } = "00000000" ;
}
如果在此時撰寫以下程式碼:
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之後,最上層陳述式的程式碼不需要包含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的特性
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
包括强类型的代码构建器,以及增量编译的支持等
泛型属性
C# 11 开始支持属性(attribute)为泛型类,即允许声明基类为System.Attribute
的泛型类:
public class GenericAttribute < T > : Attribute { }
静态接口方法
C# 11 开始允许接口中定义静态方法(包括运算符重载方法),实现该接口的类必须包含该静态方法[ 25] :
public interface IGetNext < T > where T : IGetNext < T >
{
static abstract T operator ++ ( T other );
}
无符号右移运算符 >>>
新增无符号右移运算符 >>>
,用于对带符号数进行逻辑右移 [ 26] 。
泛型数学支持
对泛型及其对象进行数学操作的支持。基于静态接口方法特性,自 .NET 8.0 起,在System
命名空间中提供数学运算相关泛型接口,以支持泛型的运算操作[ 27] :
public static TResult Sum < T , TResult > ( IEnumerable < T > values )
where T : INumber < T >
where TResult : INumber < TResult >
{
TResult result = TResult . Zero ;
foreach ( var value in values )
{
result += TResult . Create ( value );
}
return result ;
}
字符串内插中的换行符
允许内插字符串中{
与}
内的文本跨多个行
原始字符串文本
原始字符串文本以 """
开始并以 """
结束,允许多行字符串,若为多行字符串则以单独的一行 """
结束,且字符串的缩进以末尾的 """
的起始位置为基准。原始字符串文本不进行任何转义操作,但允许字符串内插(开头的 $ 数量代表内插所需要的花括号数)[ 28] :
var x = 1 ;
var y = 2 ;
var code1 = """int i = 0;""" ;
var code2 = $"""int x = {x};""" ;
var code3 = $ $"""
# include < stdio . h >
int main ( void ) {
const char * s = "{y} = {{y}}" ; // {y} = 2
return 0 ;
}
""";
Console . WriteLine ( $"code1:\n{code1}\n" );
Console . WriteLine ( $"code2:\n{code2}\n" );
Console . WriteLine ( $"code3:\n{code3}\n" );
UTF-8 字符串字面量
可以对字符串字面量指定 u8
后缀来指定 UTF-8 字符编码的字符串字面量,其类型为ReadOnlySpan<byte>
[ 29] :
列表模式
使用[
和]
可以定义列表模式,用于模式匹配 :
int [] numbers = { 1 , 2 , 3 };
Console . WriteLine ( numbers is [ 1 , 2 , 3 ]); // True
Console . WriteLine ( numbers is [ 1 , 2 , 4 ]); // False
Console . WriteLine ( numbers is [ 1 , 2 , 3 , 4 ]); // False
Console . WriteLine ( numbers is [ 0 or 1 , <= 2 , >= 3 ]); // True
数值 IntPtr
和 UIntPtr
C# 11 起 nint
和 nuint
类型的别名分别为 IntPtr
和 UIntPtr
(C# 9 中它们仅被认为是“相似”的[ 30] )。
改进了方法组向委托的转换
优化了方法组向委托转换的性能。例如下述代码中,在 C# 11 前,Sum
比 SumMethodGroup
性能更高[ 31] :
static readonly List < int > Numbers = Enumberable . Range ( 0 , 100 ). ToList ();
public int Sum ()
{
return Numbers . Where ( x => Filter ( x )). Sum (); // <- faster
}
public int SumMethodGroup ()
{
return Numbers . Where ( Filter ). Sum (); // <- slower
}
static bool Filter ( int number )
{
return number > 50 ;
}
params
集合
params
修饰符不再仅限于数组类型。现在可以将 params
用于任何已识别的集合类型,包括 System.Span<T>
、System.ReadOnlySpan<T>
以及实现 System.Collections.Generic.IEnumerable<T>
并具有 Add
方法的类型。除了具体类型外,接口 System.Collections.Generic.IEnumerable<T>
、System.Collections.Generic.IReadOnlyCollection<T>
、System.Collections.Generic.IReadOnlyList<T>
、System.Collections.Generic.ICollection<T>
和 System.Collections.Generic.IList<T>
也可以使用。[ 33]
当使用接口类型时,编译器会合成提供的参数的存储。详情请参考Params collections的功能规范 。
新的锁对象
.NET 9 运行时引入了一种新的线程同步类型 System.Threading.Lock
,该类型通过其 API 提供了更好的线程同步。Lock.EnterScope()
方法进入一个排他作用域,返回的 ref struct
支持 Dispose()
模式以退出排他作用域。C# 的 lock
语句识别 Lock
对象,并使用更新的 API,而不是传统的 System.Threading.Monitor
API。如果将 Lock
对象转换为其他类型,编译器会生成基于 Monitor
的代码。[ 34] 详情请参考该对象的功能规范 。
新的ESCAPE
转义序列
可以使用 \e
作为 ESCAPE
字符 ( Unicode U+001B
) 的字面值 转义序列。 在该版本以前,ESCAPE
使用的是 \u001b
或 \x1b
。[ 35]
不建议使用 \x1b
,因为如果 1b
后面的下一个字符是有效的十六进制数字,则那些字符会成为转义序列的一部分。[ 35]
方法组自然特性
该特性对涉及方法组的重载解析进行了小幅优化。方法组是指具有相同名称的所有重载方法。此前,编译器会构建方法组的完整候选方法集,并从中确定自然类型。新的行为是在每个作用域修剪候选方法集,移除不适用的方法(通常是具有错误泛型参数或不满足约束的泛型方法)。如果在给定作用域中找到的所有候选方法都不匹配,则方法组没有自然类型。[ 36]
以下是新行为的具体改进:
优化了方法组自然类型的确定:
按作用域逐步考虑候选方法(首先是实例方法,然后是每个后续作用域的扩展方法)。
修剪没有成功机会的候选方法,以免它们干扰确定唯一签名:
当没有提供类型参数时,修剪泛型实例方法(如 var x = M;
)。
基于是否能减少扩展和约束来修剪泛型扩展方法。
在 C# 10 中,方法组获得了一种弱自然类型。这种类型是“弱类型”,仅在方法组未被目标类型化时才会发挥作用(即它在 System.Action a = MethodGroup;
中不起作用)。这种弱自然类型允许诸如 var x = MethodGroup;
的场景。[ 39]
方法组在所有候选方法具有共同签名时具有自然类型。如果方法组可能包含扩展方法,则候选方法包括包含类型和所有扩展方法作用域。
在实践中,这意味着我们将:
构建所有候选方法的集合:
如果方法在相关类型上,如果它们是静态的且接收者是类型,或者它们是非静态的且接收者是值,则这些方法在集合中。
可以减少的所有作用域中的扩展方法也在集合中。
如果所有候选方法的签名不匹配,则方法组没有自然类型。
如果结果签名的参数数量与提供的类型参数数量不匹配,则方法组没有自然类型。
否则,结果签名将用作自然类型。
原则是按作用域逐步进行,并尽早修剪我们知道无法成功的候选方法(与重载解析中使用的原则相同)。
对于每个作用域,我们构建所有候选方法的集合:
对于初始作用域,如果方法在相关类型上且其参数数量与提供的类型参数数量匹配,并且满足提供的类型参数的约束,则这些方法在集合中;如果它们是静态的且接收者是类型,或者它们是非静态的且接收者是值。
对于后续作用域,如果扩展方法可以用提供的类型参数替换,并使用接收者的值进行减少,同时满足约束,则这些方法在集合中。
如果在给定作用域中没有候选方法,则继续到下一个作用域。
如果所有候选方法的签名不匹配,则方法组没有自然类型。
否则,结果签名将用作自然类型。
如果所有作用域都已耗尽,则方法组没有自然类型。
隐式索引访问
现在可以在对象初始化表达式中使用隐式“从末尾”索引运算符 ^
[ 41] 。
例如,可以在对象初始化器中初始化数组:
var countdown = new TimerRemaining ()
{
buffer =
{
[^1] = 0 ,
[^2] = 1 ,
[^3] = 2 ,
[^4] = 3 ,
[^5] = 4 ,
[^6] = 5 ,
[^7] = 6 ,
[^8] = 7 ,
[^9] = 8 ,
[^10] = 9
}
};
上述示例创建了一个从 9 到 0 递减的数组。
在 C# 13 之前,^
运算符不能在对象初始化器中使用,必须从前面索引元素。
迭代器和异步方法中的 ref
和 unsafe
在 C# 13 之前,迭代器方法(使用 yield return
的方法)和异步方法不能声明本地 ref
变量,也不能有 unsafe
上下文。在 C# 13 中,异步方法可以声明本地 ref
变量或 ref struct
类型的本地变量,但这些变量不能跨越 await
边界访问。同样,它们也不能跨越 yield return
边界访问。这一放宽的限制使编译器能够在更多地方允许可验证的安全使用 ref
本地变量和 ref struct
类型。你可以在这些方法中安全地使用 System.ReadOnlySpan<T>
等类型。如果违反了安全规则,编译器会发出警告。[ 42]
ref struct
接口
在 C# 13 之前,ref struct
类型不能实现接口。从 C# 13 开始,它们可以实现接口。为了确保 ref
安全规则,ref struct
类型不能转换为接口类型。这是一种装箱转换,可能违反 ref
安全。[ 43]
allows ref struct
在 C# 13 之前,ref struct
类型不能作为泛型类型或方法的类型参数声明。现在,泛型类型声明可以添加反约束 allows ref struct
。这种反约束声明该类型参数提供的类型参数可以是 ref struct
类型。编译器在该类型参数的所有实例上强制执行 ref
安全规则。这使得 System.Span<T>
和 System.ReadOnlySpan<T>
等类型可以在适用的地方与泛型算法一起使用。[ 44]
详情请参阅 where
更新 和泛型约束编程指南 文章。
更多的部分成员
在 C# 13 中,可以声明分部属性和分部索引器。分部属性和索引器通常遵循与分部方法相同的规则:创建一个声明声明 和一个实现声明 。两个声明的签名必须匹配。一个限制是不能为分部属性使用自动属性声明。未声明主体的属性被视为声明声明 。[ 45] 详情请参阅Partial members 文章。
重载解析优先级
在 C# 13 中,编译器识别 OverloadResolutionPriorityAttribute
以优先选择一个重载而不是另一个。库作者可以使用此属性确保新的、更好的重载优先于现有重载。例如,你可能会添加一个性能更高的新重载。你不希望破坏使用你库的现有代码,但希望用户在重新编译时更新到新版本。你可以使用重载解析优先级来通知编译器应优先选择哪个重载。优先级最高的重载会被优先选择。此功能旨在帮助库作者在添加新重载时避免歧义。库作者应谨慎使用此属性以避免混淆。[ 46]
程序的执行
C#通常不被编译成为能够直接在计算机 上执行的二进制 本地代码。与Java 类似,它被编译成为中间代码(Microsoft Intermediate Language),然后通过.NET Framework 的虚拟机 ——被称为通用语言运行库 ——执行。
所有的.Net编程语言 都被编译成这种被称为通用中间语言 的中间代码。因此虽然最终的程序 在表面上仍然与传统意义上的可执行文件都具有“.exe”的后缀名。如果计算机上没有安装.Net Framework,那么这些程序会弹出对话框,要求用户下载.net framework。
在程序执行时,.Net Framework将中间代码翻译成为二进制机器码,从而使它得到正确的运行。最终的二进制代码被存储在一个缓冲区(Buffer)中。所以一旦程序使用了相同的代码,那么将会调用缓冲区中的版本。这样如果一个.Net程序第二次被运行,那么这种翻译不需要进行第二次,速度明显加快。
标准化
微软公司已经向ECMA 申请将C#作为一种标准。在2001年12月,ECMA发布了ECMA-334 C#语言规范。C#在2003年成为一个ISO 标准(ISO/IEC 23270)。现在有一些独立的实现正在进行,包括:
示例
C# 的Hello World程式
下面是一個在命令列上輸出Hello World 的小程序,這種程序通常作為開始學習程序語言的第一個步驟:
using System ;
namespace ConsoleApp1
{
class Program
{
static void Main ( string [] args )
{
Console . WriteLine ( "Hello World!" );
}
}
}
实现
微软正在引领开源 参考 C# 编译器和工具集的开发。 第一个编译器 Roslyn 编译成中间语言(IL),第二个编译器 RyuJIT,[ 47] 是一个 JIT(即时)编译器,它是动态的,进行动态优化并编译将 IL 转换为 CPU 前端的本机代码。[ 48] RyuJIT 是开源的,用 C++ 编写。[ 49] Roslyn 完全是用 托管代码 (C#)编写的,已经开放并且功能以 API 的形式出现。因此,它使开发人员能够创建重构和诊断工具。[ 3] [ 50] 官方实现的两个分支是 .NET Framework(闭源,仅限 Windows)和 .NET Core(开源,跨平台);它们最终融合为一个开源实现:.NET 5.0。[ 51] 在 .NET Framework 4.6 中,新的 JIT 编译器取代了前者。[ 47] [ 52]
其他 C# 编译器(其中一些包括公共语言基础结构 和 .NET 类库的实现):
游戏引擎 Unity 使用C# 作为其主要脚本语言。由于Microsoft 捐赠了 24,000 美元, Godot 游戏引擎实现了一个可选的 C# 模块。
参考文献
^ https://devblogs.microsoft.com/dotnet/announcing-dotnet-9/ .
^ https://learn.microsoft.com/en-gb/dotnet/csharp/whats-new/csharp-13 .
^ 3.0 3.1 The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.: dotnet/roslyn . November 13, 2019 [2021-08-16 ] . (原始内容存档 于2021-02-22) –通过GitHub.
^ CoreCLR is the runtime for .NET Core. It includes the garbage collector, JIT compiler, primitive data types and low-level classes.: dotnet/coreclr . November 13, 2019 [2021-08-16 ] . (原始内容存档 于2019-10-14) –通过GitHub.
^ Rich Hickey Q&A by Michael Fogus . [2017-01-11 ] . 原始内容存档于2017-01-11.
^ 6.0 6.1 Using C# 3.0 from .NET 2.0 . Danielmoth.com. 2007-05-13 [2012年10月4日] . (原始内容 存档于2012-09-29).
^ 存档副本 . [2018-09-06 ] . (原始内容存档 于2018-01-22).
^ 8.0 8.1 8.2 8.3 存档副本 . [2018-09-06 ] . (原始内容存档 于2018-01-22).
^ 9.0 9.1 What's new in C# 10 . docs.microsoft.com. [2021-11-10 ] . (原始内容存档 于2022-02-08) (美国英语) .
^ Visual Studio 2022 version 17.0 Release Notes . docs.microsoft.com. [2022-06-24 ] . (原始内容存档 于2022-08-06).
^ DeDiv-VR. Visual Studio 2022 version 17.4 Release Notes . learn.microsoft.com. 2024-06-11 [2024-07-15 ] (美国英语) .
^ BillWagner. What's new in C# 12 . learn.microsoft.com. 2024-06-04 [2024-07-15 ] (美国英语) .
^ Murphy, Adrian. Visual Studio 17.8 now available! . Visual Studio Blog. 2023-11-14 [2024-07-15 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ DeDiv-VR. Visual Studio 2022 Preview Release Notes . learn.microsoft.com. 2024-09-19 [2024-09-20 ] (美国英语) .
^ An Introduction to C# Generics . [2020-09-25 ] . (原始内容存档 于2019-09-24).
^ Anonymous Methods (C#) . [2008-10-24 ] . (原始内容存档 于2008-04-17).
^ Covariance and Contravariance in Delegates (C#) . [2008-10-24 ] . (原始内容存档 于2008-10-12).
^ 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).
^ LINQ . Microsoft MSDN. 2007 [2007-08-13 ] . (原始内容 存档于2007-01-16) (英语) .
^ The Mellow Musings of Dr. T : What is a collection? . [2008-10-24 ] . (原始内容存档 于2008-12-18).
^ Partial Methods . [2007-10-06 ] . (原始内容存档 于2007-10-16).
^ 一覽 C# 7.0 中的新功能 . [2016-09-14 ] . (原始内容存档 于2018-10-02).
^ C# 11 中的新增功能 . learn.microsoft.com. [2024-3-19] . (原始内容存档 于2024-03-19) (中文(中国大陆)) .
^ 教程:探索 C# 11 功能 - 接口中的静态虚拟成员 . learn.microsoft.com. [2024-3-20] . (原始内容存档 于2024-03-19) (中文(中国大陆)) .
^ 位运算符和移位运算符(C# 参考) . learn.microsoft.com. [2024-3-20] . (原始内容存档 于2024-03-19) (中文(中国大陆)) .
^ Preview Features in .NET 6 – Generic Math . devblogs.microsoft.com. [2024-3-20] . (原始内容存档 于2024-02-01) (美国英语) .
^ C# 程序设计基础 . docs.eesast.com. [2024-3-20] . (原始内容存档 于2024-03-19) (中文(中国大陆)) .
^ Utf8 Strings Literals . learn.microsoft.com. [2024-3-20] . (原始内容存档 于2024-03-04) (美国英语) .
^ Introducing C# 11: Numeric IntPtr and UIntPtr . anthonygiretti.com. [2024-3-20] . (原始内容存档 于2023-11-30) (美国英语) .
^ C# 11 - Improved Method Group . prographers.com. [2024-3-20] . (原始内容存档 于2023-09-30) (美国英语) .
^ BillWagner. C# 13 中的新增功能 - C# 指南 - C# . learn.microsoft.com. 2024-03-25 [2024-09-20 ] (中文(中国大陆)) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ 35.0 35.1 BillWagner. C# 13 中的新增功能 - C# 指南 - C# . learn.microsoft.com. 2024-03-25 [2024-09-20 ] (中文(中国大陆)) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ dotnet-bot. Method group natural type improvements . learn.microsoft.com. 2024-09-17 [2024-09-20 ] (美国英语) .
^ 38.0 38.1 dotnet-bot. Method group natural type improvements . learn.microsoft.com. 2024-09-17 [2024-09-20 ] (美国英语) .
^ csharplang/proposals/csharp-10.0/lambda-improvements.md at main · dotnet/csharplang . GitHub. [2024-09-20 ] (英语) .
^ dotnet-bot. Method group natural type improvements . learn.microsoft.com. 2024-09-17 [2024-09-20 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ BillWagner. What's new in C# 13 . learn.microsoft.com. 2024-09-14 [2024-09-20 ] (美国英语) .
^ 47.0 47.1 The RyuJIT transition is complete! . microsoft.com. June 19, 2018 [July 20, 2021] . (原始内容存档 于July 19, 2019).
^ Managed Execution Process . microsoft.com. [July 20, 2021] . (原始内容存档 于December 23, 2017).
^ coreclr/src/jit/ . github.com. [July 20, 2021] . (原始内容存档 于January 9, 2019).
^ C# Guide . docs.microsoft.com. [2017-11-20 ] . (原始内容存档 于2022-08-13).
^ 5.0.8 . microsoft.com. [July 20, 2021] . (原始内容存档 于April 23, 2020).
^ Mitigation: New 64-bit JIT Compiler . microsoft.com. [July 20, 2021] . (原始内容存档 于April 5, 2018).
外部連結
实现 架構 共同語言基礎架構 CLI支持語言
Core家族3 元件 比較 即將推出