C♯
C#是微软推出的一种基于.NET框架和后来的.NET的、面向对象的高级编程语言。C#衍生自C和C++,继承了C和C++的强大功能,同时去掉了一些复杂特性,使其成为C语言家族中高效强大的编程语言。C#以.NET类库作为基础,拥有类似Visual Basic的快速开发能力。C#由安德斯·海尔斯伯格主持开发,微软在2000年发布了这种语言,希望借助这种语言来取代Java。C#已经成为Ecma国际和国际标准组织的标准规范。 设计目标ECMA标准列出的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# 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使用具化生成泛型参数,它允许优化和保存类型信息。[15] 泛型类中,可以用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,使用 类似于Python中使用的 // 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;
}
}
注意事项:
匿名方法匿名方法类似于函数式编程中的闭包。[16]匿名方法是通过使用 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;
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.[18] Linq语言集成查询(英語:Language Integrated Query,缩写:LINQ):[19]上下文相关关键字" 类型初始化器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 };
假设 匿名類型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表达式为强类型委托或强类型表达式树。 注意事项:
自动化属性编译器将自动生成私有变量和适当的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();
注意事项:
分部方法允许代码生成器生成方法声明作为扩展点,如果有人在另一个部分类实现了它才会被包含于原代码编译。[21]
例子: 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# 6.0的特性
表达式主体(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 的引數[22] 弃元元组/对象的解构: 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的特性
C# 7.2的特性
C# 8.0的特性
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)中或是屬性初始化式子中賦予其值,如果嘗試在其他地方設定該屬性的值,在編譯時便會遭編譯器阻止。 範例如下:在這個範例中,建立了一個 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) 定义初始化代码, 当初始化/加载时执行, 可以类比类的静态构造函数, 但是是组件级别的。
静态 lambda 表达式static 修饰符添加到 lambda 表达式或匿名方法 。这将无法捕获局部变量或实例状态,从而防止意外捕获其他变量。 分部方法扩展移除了分部方法的下述限制:
初始化表达式的简化如果创建对象的类型已知时,可以在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 的话,直接在文件开头写: 常量字符串插值const string x = "hello";
const string y = $"{x}, world!";
lambda的改进lambda 可以带 attributesf = [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的特性[23]泛型属性C# 11 开始支持属性(attribute)为泛型类,即允许声明基类为 public class GenericAttribute<T> : Attribute { }
静态接口方法C# 11 开始允许接口中定义静态方法(包括运算符重载方法),实现该接口的类必须包含该静态方法[24]: public interface IGetNext<T> where T : IGetNext<T>
{
static abstract T operator ++(T other);
}
无符号右移运算符 |