C#
C#是微軟推出的一種基於.NET框架和後來的.NET的、面向對象的高級編程語言。C#衍伸自C和C++,繼承了C和C++的強大功能,同時去掉了一些複雜特性,使其成為C語言家族中高效強大的編程語言。C#以.NET框架類庫作為基礎,擁有類似Visual Basic的快速開發能力。C#由安德斯·海爾斯伯格主持開發,微軟在2000年發布了這種語言,希望藉助這種語言來取代Java。C#已經成為Ecma國際和國際標準組織的標準規範。
編程範型 | 結構化、面向對象、泛型 |
---|---|
語言家族 | C |
設計者 | 微軟 |
實作者 | 微軟 |
面市時間 | 2000年 |
當前版本 | |
操作系統 | Windows、Linux、Mac OS X 、 Android |
許可證 |
|
文件擴展名 | .cs , .csx |
網站 | docs |
主要實作產品 | |
.NET、.NET框架、Mono、DotGNU | |
衍生副語言 | |
Cω、Spec#、Polyphonic C# | |
啟發語言 | |
C++、Java、Eiffel、Modula-3、Object Pascal | |
影響語言 | |
Clojure[5]、D語言、F#、Java 5、Nemerle、Vala |
設計目標
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++ 的影響。
版本
語言特性
- 指標(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 == ...)
{
// ...
}
// ...
});
}
委託的協變和逆變
屬性訪問器可以被單獨設置訪問級別
例子:
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
語言集成查詢(英語:Language Integrated Query,縮寫: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的特性[24]
泛型屬性
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;
}
C# 13的特性[32]
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]
以下是新行為的具體改進:
概述[37]
優化了方法組自然類型的確定:
- 按作用域逐步考慮候選方法(首先是實例方法,然後是每個後續作用域的擴展方法)。
- 修剪沒有成功機會的候選方法,以免它們干擾確定唯一簽名:
- 當沒有提供類型參數時,修剪泛型實例方法(如
var x = M;
)。 - 基於是否能減少擴展和約束來修剪泛型擴展方法。
- 當沒有提供類型參數時,修剪泛型實例方法(如
背景[38]
在 C# 10 中,方法組獲得了一種弱自然類型。這種類型是「弱類型」,僅在方法組未被目標類型化時才會發揮作用(即它在 System.Action a = MethodGroup;
中不起作用)。這種弱自然類型允許諸如 var x = MethodGroup;
的場景。[39]
方法組在所有候選方法具有共同簽名時具有自然類型。如果方法組可能包含擴展方法,則候選方法包括包含類型和所有擴展方法作用域。
應用[38]
在實踐中,這意味着我們將:
- 構建所有候選方法的集合:
- 如果方法在相關類型上,如果它們是靜態的且接收者是類型,或者它們是非靜態的且接收者是值,則這些方法在集合中。
- 可以減少的所有作用域中的擴展方法也在集合中。
- 如果所有候選方法的簽名不匹配,則方法組沒有自然類型。
- 如果結果簽名的參數數量與提供的類型參數數量不匹配,則方法組沒有自然類型。
- 否則,結果簽名將用作自然類型。
提案[40]
原則是按作用域逐步進行,並儘早修剪我們知道無法成功的候選方法(與重載解析中使用的原則相同)。
對於每個作用域,我們構建所有候選方法的集合:
- 對於初始作用域,如果方法在相關類型上且其參數數量與提供的類型參數數量匹配,並且滿足提供的類型參數的約束,則這些方法在集合中;如果它們是靜態的且接收者是類型,或者它們是非靜態的且接收者是值。
- 對於後續作用域,如果擴展方法可以用提供的類型參數替換,並使用接收者的值進行減少,同時滿足約束,則這些方法在集合中。
- 如果在給定作用域中沒有候選方法,則繼續到下一個作用域。
- 如果所有候選方法的簽名不匹配,則方法組沒有自然類型。
- 否則,結果簽名將用作自然類型。
- 如果所有作用域都已耗盡,則方法組沒有自然類型。
隱式索引訪問
現在可以在對象初始化表達式中使用隱式「從末尾」索引運算符 ^
[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]
更多的部分成員
在 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 類庫的實現):
- 微軟的轉軸項目(Rotor Project,目前稱為Shared Source Common Language Infrastructure),提供了通用語言執行層(Common Language Runtime)的實作與C# 編譯器。但是Shared Source Common Language Infrastructure在2006年的2.0版後就停止了。
- 由Microsoft贊助的Mono 專案提供了C# 編譯器,它提供了一個開源 C# 編譯器、一個完整的 CLI 開源實現,同時也接近百分之百地實作了.NET Framework類別庫。而Mono後來衍伸出由微軟認可的第三方套件Xamarin。
- Dot GNU 專案(現已停產)也提供了另一個自由版本的C# 編譯器,也提供了.NET Framework類別庫的實作。
遊戲引擎 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).
外部連結
- (英文)C# Language Specification (MSDN)(頁面存檔備份,存於網際網路檔案館)
- (英文)ECMA-334 C# Language Specification (.pdf)
- (英文)ISO/IEC 23270:2003 C# Language Specification(頁面存檔備份,存於網際網路檔案館)
- (英文)Visual Studio Code(頁面存檔備份,存於網際網路檔案館)
- (英文)Download .NET(頁面存檔備份,存於網際網路檔案館)
- Visual C# .NET入門 (MSDN)(頁面存檔備份,存於網際網路檔案館)
- MCS: Mono C# 編譯器(頁面存檔備份,存於網際網路檔案館)
- Visual Studio Community(頁面存檔備份,存於網際網路檔案館)
- Visual C# Logo(頁面存檔備份,存於網際網路檔案館)
- C#教學(頁面存檔備份,存於網際網路檔案館)
- C# 教材目錄(頁面存檔備份,存於網際網路檔案館)