C#版本 | 釋出時間 | .NET 版本 | VS版本 |
---|---|---|---|
C#1.0 | 2002-02-13 | .NET Framework 1.0 | VS.NET 2002 |
C#1.1 C#1.2 | 2003-04-24 | .NET Framework 1.1 | VS.NET 2003 |
C#2.0 C#3.0 | 2005-11-07 | .NET Framework 2.0 | VS2005 |
C#3.0 | 2006-11 | .NET Framework 3.0 | VS2008 |
C#3.0 (導入LinQ) | 2007-11-19 | .NET Framework 3.5 | VS2008 |
C#4.0 | 2010-4-12 | .NET Framework 4.0 | VS2010 |
C#5.0 | 2012-02-20 | .NET Framework 4.5 | VS2012 |
C#5.0 | 2013-10-17 | .NET Framework 4.5.1 | VS2013 |
C#5.0 | 2014-05-05 | .NET Framework 4.5.2 | VS2013 |
C#6.0 | 2015-07-26 | .NET Framework 4.6 | VS2015(v14) |
C#6.0 | 2015-11-30 | .NET Framework 4.6.1 | VS2015(v14) |
C#7.0 | 2016-08-02 | .NET Framework 4.6.2 | VS2017(v15) |
C#7.1 | 2017-04-05 | .NET Framework 4.7 | VS2017(v15.3) |
C#7.2 | 2017-10-17 | .NET Framework 4.7.1 | VS2017(v15.5) |
C#7.3 | 2018-04-30 | .NET Framework 4.7.2 (對應 .Net Core 2.X) | VS2017(v15.7) |
C#8 | 2019-04-18 | .NET Framework 4.8 (對應 .Net Core 3.X) | VS2019(v16) |
C#9 | 2020-11 | .NET 5.0 | VS2019(16.8) |
C#10 | 2021年11-08 | .NET 6.0 ~ 6.0.36 | VS2022(17.0) |
C#11 | 2022-11-8 | .Net 7.0.0 ~7.0.20 | VS2022(17.4) |
C#12 | 2023年11月14日 | .Net 8.0.0 | |
C#13 | 2024年12月3日 | .Net 9.0.0 |
僅提供最基本的功能 , 此版本連泛型都沒有了 , 就更不用說 LinQ 了…
開始支援泛型(Generic)
將 class、struct、interface,分割到兩個以上的來源檔案。 每一個來源檔案都包含型別或方法定義的一個區段,而當編譯應用程式時,就會將所有區段結合起來。
int? i = null;
int? i = null;
// 當 i 是 null 時 ,
// 則繼續查找 ?? 右邊的字符是否為 null , 若否 , 則回傳該值. 因此 j=3
int j = i ?? 3;
public class MyClass
{
private int _myField = 5;
public int MyProperty
{ // get set 皆是 accessor
// 設定 set 的存取權限為 protected
get { return _myField; }
protected set { _myField = value; }
}
}
var o = new object();
bool isA = o is A(ClassName); // 回傳 True or False`
as 運算子將運算式的結果明確地轉換成給定參考或可為 Null 的實值型別。 如果無法轉換,則 as 運算子會傳回 null
引用函數式語言設計的概念 , LINQ 在此版被導入 , 不嫌棄 , 請參考我的筆記
// 舊的寫法
private string _property = string.Empty;
public string Property
{
get { return _property; }
set { _property = value; }
}
// 新的寫法
public string Property { get; set; }
// 建立匿名型別物件 , 其具有屬性為 Name , Age
var value = new { Name = "王曉明", Age = 5 };
// 建立匿名型別陣列 , 其每一個成員(物件)都具有屬性為 Name , Age
var values = new[] { new { Name = "周論發", Age = 15 },
new { Name = "小龍女", Age = 65 }
};
// 物件初始舊的寫法
Person p = new Person();
p.Name = "City";
// 新的寫法
Person p = new Person{ Name = "City" }
// 集合初始舊的寫法
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
// 新的寫法
var list = new List<int>() { 1 , 2 };
var s = "小名" // 自動推斷變數 s 為 string
主要 Feature 為 Dynamic 關鍵字的導入.
public static void CityMethod(string name, int age)
{
Console.WriteLine($"{name}的年紀是 {age} 歲");
}
public static void Main(string[] args)
{
// 即使傳入參數位置順序相反 , 但只要有指名參數名稱 , 仍可正確傳入參數
CityMethod(age: 29, name: "楊過");
Console.ReadKey();
}
public static void CityMethod(string name, int age = 15)
{
Console.WriteLine($"{name}的年紀是 {age} 歲");
}
public static void Main(string[] args)
{
// 若未傳入參數 age , 則使用預設的 15
CityMethod("楊過");
Console.ReadKey();
}
var task = new Task(Action);
task.Start(); // 開始執行 Action
var task = new Task(Action);
task.Start();
task.Wait(); // Block UI 等待 task 完成
// 回傳 "不用 call Start , 就會執行 Action" 的 task
var task = Task.Factory.StartNew(Action);
private void Button1_Click(object sender, EventArgs e)
{
var backgroundScheduler = TaskScheduler.Default;
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(delegate { DoBackgroundComputation(); },
backgroundScheduler).
ContinueWith(delegate { UpdateUI(); }, uiScheduler).
ContinueWith(delegate { DoAnotherBackgroundComputation(); },
backgroundScheduler).
ContinueWith(delegate { UpdateUIAgain(); }, uiScheduler);
}
var task = Task.Factory.StartNew(()=>"result data");
var result = task.Result;
參考基於 Task 的非同步設計的概念 , async 和 await 這兩個關鍵字被導入. 印象中 C# 是最早使用這兩個關鍵字的語言.
屬性 | 描述 | 類型 |
---|---|---|
CallerFilePathAttribute | 包含呼叫端的原始程式檔完整路徑。 完整路徑是在編譯時期的路徑。 | String |
CallerLineNumberAttribute | 原始程式檔中呼叫方法的行號。 | Integer |
CallerMemberNameAttribute | 呼叫端的方法名稱或屬性名稱。 | String |
CallerArgumentExpressionAttribute | 引數運算式的字串表示。 | String |
使用 Caller Info 屬性,您就可以取得有關方法之呼叫端的資訊。 您可以取得原始程式碼的檔案路徑、原始程式碼中的行號,以及呼叫端的成員名稱
public abstract class NotifyPropertyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)=>
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(propertyName)
);
// 透過 CallerMemberName 去取得呼叫端的名稱 (呼叫端可以是方法或者是屬性)
protected void SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value)) return;
storage = value;
OnPropertyChanged(propertyName);
}
}
主要是語法使用上的改進.
// C# 2.0
private string _property = string.Empty;
public string Property
{
get { return _property; }
private set { _property = value; }
}
// C# 3.0
public string Property { get;private set; }
// C# 6.0
public string Property { get; }
public string Property { get; set; } = "自動實作的屬性可以設定初始值了"
public string GetFullName(string name) => $"我的名字是 {name}";
// 等同唯讀屬性 , 因為沒有設定 set.
public string FullName => GetFullName("王大力");
// 匯入 Math 類別所有的靜態方法 , 實體方法會忽略之
using static System.Math;
class Person
{
public string Name { get; set; } = "小王";
}
public static void Main(string[] args)
{
List<Person> people = null;
people = new List<Person>() { null, new Person() };
// 由左到右 , 第一個 ? 檢查 people 是否為 null , 若為 null 則回傳 null
// 第二個 ? 檢查 people[1] 是否為 null , 若為 null 則回傳 null
var firstPersonName = people?[1]?.Name;
Console.WriteLine(firstPersonName);
Console.ReadKey();
}
public string LastName { get; set; } = "小名";
public string FirstName { get; set; } = "王";
public string FullName => $"{FirstName} {LastName}";
abstract class Base { }
class Circle : Base { }
class Rectangle : Base { }
public static void Main(string[] args)
{
var graph = new Rectangle();
try {
throw new Exception();
}
catch (Exception e) when (e.Message != null && graph is Circle ) {
Console.WriteLine($"Circle Exception");
}
catch (Exception e) when (e.Message != null && graph is Rectangle) {
Console.WriteLine($"Rectangle Exception");
}
Console.ReadKey();
}
public string Property { get; set; }
public int _field;
public void Method() { }
public static void Main(string[] args)
{
Console.WriteLine(nameof(Property));
Console.WriteLine(nameof(_field));
Console.WriteLine(nameof(Method));
}
try{
// doSomeThing
}
catch (Exception)
{
await Task.Delay(1000);
}
finally
{
await Task.Delay(1000);
}
// 舊的寫法
Dictionary<int, string> webErrors = new Dictionary<int, string>
{
{ 404, "Page not Found"},
{ 302, "Page moved, but left a forwarding address."},
// 404 索引子重複 , 會跳 Exception
{ 404, "The web server can't come out to play today."}
};
// 新的寫法
Dictionary<int, string> webErrors = new Dictionary<int, string>
{
[404] = "Page not Found",
[302] = "Page moved, but left a forwarding address.",
// 發現索引子已存在 , 會蓋過原本索引子的值 , 不會跳 Exception
[404] = "The web server can't come out to play today."
};
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public static void Add(this ICollection<Person> source, string name, int age)
{
source.Add(new Person { Name = name, Age = age });
}
public static void Main(string[] args)
{
// new Person 的動作 , 交給 Add 方法做掉了.
// 自定義類別的集合初始設定式
var people = new List<Person>()
{
{"小王",15 },
{"大王",64 },
};
Console.ReadKey();
}
主要是語法使用上的改進. 改進幅度比 6.0 更大. 新的語法與語法之間可以交互使用.
public static void SetValue(out int v) => v = 3;
static void Main()
{
// 舊的寫法
int v;
SetValue(out v);
// 新的寫法 , 支援變數型別推斷 , 不想使用 , var 可以改 int
SetValue(out var v2);
}
C# 7.0 加入了 Tuple 的語言支援,讓 Tuple 欄位的語意名稱能使用全新且更具效率的 Tuple 型別。
public static (string name, int age, bool successs) GetData(
(string name, int age) data
)
{
var (name, age) = data; // deconstruct data into two variable name & age
return (name, age, true);
}
static void Main()
{
var valueTuple = GetData(("王大明", 15));
}
通常在您解構 Tuple 或以 out 參數呼叫方法時,會強制您要定義變數,而您沒有使用該值的打算。 C# 新增了對捨棄的支援,來應付這種狀況。 捨棄是僅限寫入的變數,其名稱為 _ (底線字元);您可將所有想要捨棄的值指派到單一變數。 捨棄類似於未經指派的變數;和指派陳述式一樣,都不能用於程式碼中。
public static (string name, int age, bool successs) GetData(
(string name, int age) data
)
{
var (name, age) = data;
return (name, age, true);
}
static void Main()
{
// just record success by variable isSuccess
var (_, _, isSuccess) = GetData(("王大明", 15));
Console.WriteLine($"get Data success is {isSuccess}");
}
模式比對支援 is 運算式和 switch 運算式。 每個都讓您能檢查物件和其屬性,以判斷該物件是否符合搜尋的模式 , 並在一個指令後得到結果。 同時亦可以使用 when 關鍵字在 switch 運算式上
if (input is int count)
sum += count;
public class MyClass { public string Name { get; set; } }
static string Show(MyClass instance)
{
switch (instance)
{
case null:
throw new NullReferenceException();
case MyClass c1 when c1.Name != null:
return $"MyClass 's Name is {c1.Name}";
case MyClass _:
return "MyClass 's Name is Empty";
default:
throw new InvalidOperationException("Unrecognized type");
}
}
static void Before()
{
// unsafe 需要去 專案 => 專案屬性 => 建置 => 不安全程式碼打勾
int number = 100;
unsafe
{
int* p = &number;
*p = 999;
}
}
static void After()
{
int number = 100;
ref int p = ref number;
p = 999;
}
static string OutputDayString(string freq = "weekend")
{
var dayStr = IsEveryDay(freq) ?? IsWeekday(freq) ?? IsWeekend(freq) ?? "has error";
return dayStr;
// local functions
string IsEveryDay(string f) => f == "everyDay" ? "is everyDay ~" : null;
string IsWeekday(string f) => f == "weekday" ? "is weekday ~" : null;
string IsWeekend(string f) => f == "weekend" ? "is weekend ~" : null;
}
static void Main()
{
var dayString = OutputDayString();
Console.WriteLine(dayString);
}
public class MyClass
{
private string _name = string.Empty;
public MyClass(string name) => this._name = name;
~MyClass() => Console.WriteLine("do some thing");
public string Name
{
get => _name;
set => _name = value;
}
}
throw 原本只能做為陳述式使用 , 但現在也可以當作運算式使用.
void ThrowException() => throw new Exception(); //能使用 => 運算子
// 能使用三元運算子
string result = value != string.Empty ?
"correct" : throw new Exception("value is empty");
// 能使用 null 聯合運算子
return value ?? throw new Exception("value is null");
從非同步方法傳回 Task 物件可能會造成效能瓶頸。 Task 是參考型別,因此使用它表示要配置物件。 當使用 async 修飾詞宣告的方法傳回結果時,或是以同步方式完成,額外配置記憶體給物件可能會效能關鍵的程式中變成一項重要的時間成本。 例如 : 如果這些配置發生在緊密迴圈中,它可能會變得成本很高。
async 方法傳回型別除了 Task、Task<T> 和 void 外 , 新增一個 ValueTask<T>。
需要再 NuGet 安裝 System.Threading.Tasks.Extensions 才能使用該 ValueTask<TResult>
Numeric Literal Syntax Improvements
public const int Eight = 0b00001_000;
public const int Sixteen = 0x1_0;
public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const long BillionsAndBillions = 100_000_000_000;
public static void MyMethod<TSource>()
{
// old
var result = default(TSource);
// new
TSource result2 = default;
}
// old 寫法
string s = "";
int i = 5;
var t = (s: s, i: i);
// new 寫法
// 之後可使用 t.s or t.i 去使用 tuple 項目 ,
// C#7.1之前 , 若未替 tuple 項目命名 , 則只能使用 .item1 , .item2 ...
string s = "";
int i = 5;
var t = (s, i);
public class MyClass { public string Name { get; set; } }
public class MyClass2 { public string Name { get; set; } }
static string Show<T>(T instance)
{
switch (instance)
{
case MyClass c1 when c1.Name != null:
return $"MyClass 's Name is {c1.Name}";
case MyClass _:
return "MyClass 's Name is Empty";
case MyClass2 c2 when c2.Name != null:
return $"MyClass2 's Name is {c2.Name}";
case MyClass2 _:
return "MyClass2 's Name is Empty";
default:
return "甚麼玩意";
}
}
Safe Efficient Code Enhancements
// 通常會跟 Optional 參數混用.
static void Main(string[] args)
{
Test("小名", phone: "0989782266"); // 不用把所有的 Optional 參數都打完.
Console.ReadKey();
}
public static void Test(string name, int age = 30, string phone = null, [CallerMemberName] string caller = null)
{
Console.WriteLine($"{name} {age} {phone} {caller}");
}
Leading Underscores In Numeric Literals
int binaryValue = 0b_0101_0101;
Private Protected Access Modifier
ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);
var obj = new object();
var obj2 = new object();
ref var i = ref obj;
i = ref obj2; // i 重新指派給 obj2
允許 Enum、Delegate 和 MulticastDelegate 作為基底類別條件約束
You can find me on
若有謬誤 , 煩請告知 , 新手發帖請多包涵