C# 文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/

C# 7

Tuples(元组)

c#
(string Alpha, string Beta) namedLetters = ("a", "b"); //等效 var alphabetStart = (Alpha: "a", Beta: "b"); Console.WriteLine(alphabetStart.Alpha);

解构函数(Deconstruct)

取对象的属性,解构为元组

c#
public class User { public string Name { get; set; } public string Email { get; set; } public int Age { get; set; } public string Sex { get; set; } //解构函数 public void Deconstruct(out string name,out string email) { name = Name; email = Email; } } //使用 (var name, var email) = user;

弃元:符号'_'

弃元相当于未赋值的变量;它们没有值。 因为只有一个弃元变量,甚至不为该变量分配存储空间,所以弃元可减少内存分配。

在元组:

c#
(_,_,c)=GetFullName();

在out调用方法:

c#
public void on(out int i,out int j){} on(out _ ,out int j);

适用:

  • 在对元组或用户定义的类型进行解构时

  • 在使用 out 参数调用方法时

  • 在使用 is 和 switch 语句匹配操作的模式中

  • 在要将某赋值的值显式标识为弃元时用作独立标识符

模式匹配

c#
//在 is 中 foreach(var item in values) { if (item is int val) //此处直接赋值 val sum += val; } //在 case 中 foreach (var item in values) { switch (item) //item 类型为object { case int val: //此处直接赋值 val sum += val; break; } } //在switch中(C# 7.1) switch (coll) { case Array arr when arr.Length > 0: Console.WriteLine($"An array with {arr.Length} elements."); break; case IEnumerable<int> ieInt: Console.WriteLine($"Average: {ieInt.Average(s => s)}"); break; case IList list: Console.WriteLine($"{list.Count} items"); break; case null: // Do nothing for a null. break; default: Console.WriteLine($"A instance of type {coll.GetType().Name}"); break; }

本地函数

即函数内部函数

以下是对迭代器的示例,对async同样适用,以确保在工作开始之前引发由参数验证引起的异常

  • 对迭代器

    c#
    public static IEnumerable<char> AlphabetSubset3(char start, char end) { if (start < 'a' || start > 'z') throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); if (end < 'a' || end > 'z') throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); if (end <= start) throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); return alphabetSubsetImplementation(); //for (var c = start; c < end; c++) // yield return c; //yield 在此,调用方法只会创建迭代器 而不会访问数据,在迭代器遍历时才会访问,所以上面的判断不能立即生效 IEnumerable<char> alphabetSubsetImplementation() { for (var c = start; c < end; c++) yield return c; //在内部函数中 上面的判断会直接生效,在外面(由于是一个迭代-每次一个,不达到位置时不会报错) } }
  • 对async

    c#
    public Task<string> PerformLongRunningWork(string address, int index, string name) { if (string.IsNullOrWhiteSpace(address)) throw new ArgumentException(message: "An address is required", paramName: nameof(address)); if (index < 0) throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException(message: "You must supply a name", paramName: nameof(name)); return longRunningWorkImplementation(); async Task<string> longRunningWorkImplementation() { var interimResult = await FirstWork(address); var secondResult = await SecondStep(index, name); return $"The results are {interimResult} and {secondResult}. Enjoy."; } }

异步Main方法

csharp
static async Task<int> Main() { // This could also be replaced with the body // DoAsyncWork, including its await expressions: return await DoAsyncWork(); }

更多的 expression-bodied

csharp
// Expression-bodied constructor public ExpressionMembersExample(string label) => this.Label = label; // Expression-bodied finalizer ~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!"); private string label; // Expression-bodied get / set accessors. public string Label { get => label; set => this.label = value ?? "Default label"; }

throw可以在表达式中使用

数字分隔符

可以使用符合'_'分割数字,用以显示,方便阅读

适用于整数、小数、各种进制表示

c#
int val=0b0001_0001 //二进制 int val=0xFE_AB_12 //16进制

ref、out、in

ref:允许使用并返回对变量的引用,修改会更改源值。

in:按引用传递参数,但调用的方法不修改值。

out:按引用传递参数,必须在返回前初始化值。

c#
int[] array = new int[] { 1, 2, 3 }; ref int val = ref array[1]; //此时对val的赋值会改变array[1]的值 static void MethodIn(in string arg){};//不允许修改值 static void MethodOut(out string arg){};//参数需要在方法返回前初始化 static void MethodRef(ref string arg){};//参数需要在调用前初始化 //ref 作为返回值 static ref int Find(int[] array, Func<int,bool> predicate){} ref var item=Find(array,(val)=>val==2); item=10;//此时对item的赋值会改变array[1]的值 //ref 用于表达式 int num=2; ref var r=ref(array!=null? ref arrary[0]: ref num);

fixed

fixed 语句可防止垃圾回收器重新定位可移动的变量。 fixed 语句仅允许存在于unsafe的上下文中,可以使用fixed关键字来创建固定长度的数组,但是数组只能是bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, double中的一种

c#
public unsafe struct MyArray { public fixed char pathName[128]; } //如果不用fixed的话,无法预先占住128个char的空间,只是包含了对数组的引用,使用fixed后可以很好的和非托管代码进行交互。

访问修饰符

  • public:访问不受限制。
  • protected:访问限于包含类或派生自包含类的类型。
  • internal:访问限于当前程序集。
  • protected internal:访问限于当前程序集或派生自包含类的类型。
  • private:访问限于包含类。
  • private protected:访问限于包含类或当前程序集中派生自包含类的类型。

命名参数

使用名称使用参数,不必按照参数顺序。

c#
private void CreateNewStudent(string name, int studentid = 0, int year = 1) //使用 CreateNewStudent(year:2, name:"Hima", studentid: 4);

C# 8

默认接口方法

可以将成员方法添加进接口,并提供实现。

使用强制转换层接口类型以使用默认实现,在实现接口的对象中重写以替代默认的实现。

Swicth表达式

csharp
public static RGBColor FromRainbow(Rainbow colorBand) => colorBand switch { Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00), Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00), Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00), _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),//default替换为了弃元'_' };

属性模式

c#
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 }; //用于变量声明 static string TakeFive(object input) => input switch { string { Length: >= 5 } s => s.Substring(0, 5), string s => s, ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()), ICollection<char> symbols => new string(symbols.ToArray()), null => throw new ArgumentNullException(nameof(input)), _ => throw new ArgumentException("Not supported input type."), };

元组模式

c#
static string GetPosition(int value1,int value2) =>(value1,value2) switch { (0,0) => "Left-Top Corner", (0,1) => "Left-Bottom Corner", (1,0) => "Top-Right Corner", (1,1) => "Right-Bottom Corner", };

位置模式

csharp
//在含有解构(Deconstruct)方法的类型 //当Point可以解构为元组 static Quadrant GetQuadrant(Point point) => point switch { (0, 0) => Quadrant.Origin, var (x, y) when x > 0 && y > 0 => Quadrant.One, var (x, y) when x < 0 && y > 0 => Quadrant.Two, var (x, y) when x < 0 && y < 0 => Quadrant.Three, var (x, y) when x > 0 && y < 0 => Quadrant.Four, var (_, _) => Quadrant.OnBorder, _ => Quadrant.Unknown };

using升级

using 在方法中使用,在不使用''括号的情况下,默认在方法返回之后Dispose使用了using的对象。

静态本地函数

静态本地函数不能访问所在方法的任何变量。

ref struct

ref struct 必须要有一个可访问的void Dispose()方法。

null包容运算符 '!'

仅为去除编译器null值警告,运行中无作用。

c#
Class a; a!.doSomething(); Console.WriteLine(a!);

异步流

返回IAsyncEnumerable<T>,异步流中使用 yield return返回连续的元素。

csharp
public static async IAsyncEnumerable<int> GenerateSequence() { for (int i = 0; i < 20; i++) { await Task.Delay(100); yield return i; } } //使用 await foreach (var number in GenerateSequence()) { Console.WriteLine(number); }

await using

通过实现 IAsyncDisposable 接口,标识异步可释放对象。

不适用{}的多个连续await using,会从最下方往上开始DisposeAsync 。

索引运算符

  • ^:末尾运算符(System.Index):从列表末尾开始索引,注意Array[^0]=Array[Array.Length],虽然不报错,但是超出列表范围了,运行抛IndexOutOfRangeException。
  • ..:范围运算符(System.Range):表示列表的区间,比如Array[0..^0],包括0位置但不包括^0位置,^0在范围中可用。..符号前后的数值都可以省略,表示从列表开始或结束的位置。
csharp
var str = "12345"; Console.WriteLine(str[^0]); //运行报错 Console.WriteLine(str[^str.Length]); //1 Console.WriteLine(str[..^0]); //12346 Console.WriteLine(str[1..]);//2345 Console.WriteLine(str[1..^1]);//234

??=

null合并运算符,比如 a??=new A();,如果a本来为空才赋值,否则a等于原先的值。

stackalloc

表示在堆栈上分配的内存块。所在方法返回时,将丢弃在执行期间所有的在堆栈上的内存块,不受垃圾回收影响。分配给Span<T>ReadOnlySpan<T>时,不需要unsafe上下文。

如果 stackalloc 表达式的结果为 System.SpanSystem.ReadOnlySpan 类型,则可以在其他表达式中使用 stackalloc 表达式。

csharp
Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 }; var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6, 8 }); Console.WriteLine(ind); // output: 1

C# 9

init

新的属性和索引器的关键字,用于以前set关键字的地方,区别在于,一旦构造完成就变成只读。

c#
//属性 public int ReadonlyValue{ get; init;}

record

新的引用类型关键字,算是升级版的struct,可以继承自其他record,可以直接用==判断值相等,对于其中引用类型字段则判断其是否引用同一个对象。子record未实现自己的字段时,虽所有字段都和父record相同,实例化后 子recode != 父recode,更不用说其他recode了。

c#
//声明1 public record Person(string FirsetName,string LastName);//默认字段是init //声明2 public record Person { public string FirstName { get; init; } public string LastName { get; init; } } //声明1和声明2除了构造不同,字段属性相同 //使用 Person p1=new("Hello", "World"); //声明1实例化 Person p2=new Person{FirstName="Hello", LastName="World"};//声明2实例化 //p1==p2 (True) Console.WriteLine(p2); // output: Person { FirstName = Hello, LastName = World }

with

新的表达式关键字,创建实例的一个副本,修改指定字段的,针对 init

c#
Person p=new ("Hello","world"); Person pw= p with {LastName="you"}; Console.WriteLine(pw); // output: Person { FirstName = Hello, LastName = you }

顶级语句(全局代码)

应用中只能有一个文件中使用顶级语句。

目的时代替传统的Main方法,所以可以访问原来的args参数。如果有全局代码也有Main函数,则默认忽略Main

可以用来创建类似脚本的东西。

命名空间为全局命名空间。

c#
//传统方式 using System; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } } } //顶级语句 using System; Console.WriteLine("Hello World!"); //或者 System.Console.WriteLine("Hello World!"); //可以使用await调用异步方法 await System.Threading.Tasks.Task.Delay(1000); //可以return一个int数值表示程序结束 return 0; //在下方可以定义类或其他,必须在全局代码下方

使用关键字和传统Main方法对比:

顶级语句使用的关键字 传统Main方法
await and return static async Task<int> Main(string[] args)
await static async Task Main(string[] args)
return static int Main(string[] args)
No await or return static void Main(string[] args)

模式匹配

  • 类型模式要求在变量是一种类型时匹配
  • 用圆括号强制匹配优先级
  • and 模式要求两个模式都匹配
  • or 模式要求任一模式匹配
  • not 模式要求模式不匹配
  • 关系模式要求输入小于、大于、小于等于或大于等于给定常数。
c#
public static bool IsLetterOrSeparator(this char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ','; //更常用的非null判断 if (e is not null) { // ... }

nint 和nuint

新的整数类型,表示本机大小的整数,32位进程中表示32位的整数,64位的进程中表示64位的整数,有助于提高整数运算的性能。

nint和nuint在内部表示为IntPtr和UintPtr。

但 下面两个并不等效。

c#
nint a=1; System.IntPtr a=1;

new()

创建类型或者传递参数时可以使用new()创建新实例。

c#
class Person{ public string Name{get;set;} public Person Create(){ return new(); } } class Client{ public void Update(Person p); } Person p=new(){Name="Xo"}; Client c=new(); //c.Update(p); c.Update(new());

代码生成器

代码生成器在编译期间可以分析编译对象和提供额外的代码作为编译的输入。

一个简单的理解是关于反射的,以前的反射都是在运行期,有了代码生成器,完全可以放在编译期处理,节省了首次运行 的成本。

示例项目:SourceGenerators · GitHub

  1. 实现ISourceGeneratorb 并使用[Generator]特性标识,定义自己的代码生成器,在其中添加要生成的代码。
  2. 配置目标项目文件.csproj,添加对生成器项目引用,配置OutputItemType="Analyzer"ReferenceOutputAssembly属性。
  3. 生成后可以在其他代码中使用它们。

其他

  • static 可应用于Lambda表达式和匿名方法。
  • foreach 可以识别扩展方法了,为任何类型实现GetEnumerator扩展方法,都可以使用foreach了。
  • 弃元_可以作为Lambda表达式的参数。
  • 属性可应用于本地函数的参数。
  • 重写方法可返回 从返回类型派生的类型。

C# 10

record

当前可以使用 record structreadonly record struct声明值类型的record。 使用 record class(默认,相当于单独使用record) 声明引用类型的record。

record 的 ToString()方法,可以使用 sealed修饰,派生的记录不能重写ToString()

csharp
public readonly record struct Point(double X, double Y, double Z);

struct

终于! struct 可以有无参数构造了,可以在这个构造中设定属性或字段的默认值。

with

with 左侧的类型可以是 struct 或者 匿名类型了。

csharp
// struct public struct Point { public int X {get;set;} public int Y {get;set;} public int Z {get;set;} } var p1 = new Point {X=1, Y=2, Z=3}; var p2 = p1 with { X=2 }; //匿名类型 var apple = new { Item = "apples", Price = 1.35 }; var halfApple = apple with { Price = 0.79 };

内插字符串-- $"

当前可以自定义自定义内插处理程序,使用 [InterpolatedStringHandler] 特性。 教程

常量也可以使用内插字符串生成:

csharp
const string Language = "C#"; const string Platform = ".NET"; const string Version = "10.0"; const string Name = $"{Platform} - Language: {Language} Version: {Version}";

using

namespace声明,省去一对{},缩进少一层。

注意:使用新的声明方式,同一个文件中不能再声明其他的命名空间(嵌套到类或者接口这种有{}的之中再声明命名空间是可以的)。

csharp
namespace MyNameSpace; class Class1{} interface Interface1{}

global using

可以使用 global 修饰 using 了,中间还可以带上 static,比如 global using static System.Math; global using xxxNameSpace 意味着,在当前项目中,不需要在各个文件中使用 using xxxNameSpace;这种方式了,可直接使用它里面的类型。 效果等同于在项目文件(.csproj)中增加 <Using Include="xxxNameSpace"/>

.csproj

xml
<ItemGroup> <!-- global using My.Awesome.Namespace1;--> <Using Include="My.Awesome.Namespace1" /> <!-- global using static My.Awesome.Namespace2;--> <Using Include="My.Awesome.Namespace2" Static="True" /> <!-- global using Space3 = My.Awesome.Namespace2;--> <Using Include="My.Awesome.Namespace3" Alias="Space3"/> </ItemGroup>

扩展属性

csharp
if (e is MethodCallExpression { Method.Name: "MethodName" }) // c# 10 //等同于 if (e is MethodCallExpression { Method: { Name: "MethodName" } }) // c# 8

lambda 表达式

lambda表达式可使用 var 关键字声明,自动解释为推断的自然类型(FuncActionDelegateObject)。

csharp
var parse = (string s) => int.Parse(s); //parse is Func<s,int> Func<string, int> parse = s => int.Parse(s); //这句 s 的类型无法推断,需要声明类型

析构取消限制

csharp
//可以随意拆分声明 int x = 0; (x, int y) = point;

CallerArgumentExpression 参数特性

System.Runtime.CompilerServices.CallerArgumentExpressionAttribute 根据条件主要用于开发诊断,输出信息为表达式字符,而不是值

csharp
public static void ValidateArgument( string parameterName, bool condition, [CallerArgumentExpression("condition")] string? message=null) { if (!condition) { throw new ArgumentException($"Argument failed validation: <{message}>", parameterName); } } ValidateArgument(null);// output : Argument failed validation: <func is not null>

C# 11

.Net 7 以上

泛型特性

特性支持泛型,不知道有什么用,难道只是方便取类型?

比如下方的 [Generic<T>()]

其中T必须是明确的类型,不能是泛型类的泛型参数。不能是 dynamicstring?(任何可为null的引用类型, int?可以)、(int x, string)

object代替dynamic,用string代替string?,用ValueTuple<int,string>代替(int x, string)

csharp
[AttributeUsage(AttributeTargets.Property)] public class GenericAttribute<T>:Attribute { public Type Type => typeof(T); } public class AClass { [Generic<int?>()] public int Value { get; set; } }

泛型数学支持

  • 新增System.Numerics.INumber<TSelf> 系列接口,用于支持数值类型的操作,而不是以往区分intfloat等分别操作。
  • 新增>>>无符号右移,值始终为正数。
  • 不再要求x >> y中y的值是int类型。
  • 可定义包含checkedunchecked的运算符重载
csharp
//静态接口成员 public interface IGetNext<T> where T : IGetNext<T> { static abstract T operator ++(T other); //静态抽象方法,用于定义++运算符 } //含checked的运算符重载 public record struct Point(int X, int Y) { public static Point operator checked +(Point left, Point right) { checked { return new Point(left.X + right.X, left.Y + right.Y); } } public static Point operator +(Point left, Point right) { return new Point(left.X + right.X, left.Y + right.Y); } }

nint 和 nuint

  • nint 对应 System.IntPtr
  • nuint 对应 System.UIntPtr

字符串内插

允许 {} 中出现合法C#语句

csharp
string name = "Horace"; int age = 34; Console.WriteLine($"He asked, \"Is your name {name}?\", but didn't wait for a reply :-{{"); Console.WriteLine($"{name} is {age} year{(age == 1 ? "" : "s")} old."); //这个可以,之前不能太烦了

原始字符串

以至少3个"开始,相同数量的"结束,其中开始引号之后的换行不包含在字符串中,结束引号之前的换行不包含在字符串中

csharp
string longMessage = """ //开始引号之后的换行不包含在字符串中 This is a long message. It has several lines. Some are indented more than others. Some should start at the first column. Some have "quoted text" in them. """; //结束引号之前的换行不包含在字符串中 var location = $$""" //有多少个$表示有多少个{}开始和结束内插 You are at {{{Longitude}}, {{Latitude}}} """;

列表模式

主要用来扩展模式匹配

csharp
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 if (numbers is [var first, _, _]) { Console.WriteLine($"The first element of a three-item list is {first}."); }

自动默认结构

struct 里面的字段可以不初始化,由编译器自动初始化其默认值。

UTF-8字符串

csharp
ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8; //非编译时常量,运行时常量。不能作为方法参数的默认值。

required 关键字

required 成员必须在创建对象时初始化

csharp
public class Person { public Person() { } [SetsRequiredMembers] //表示此构造已初始化所有必要成员,不需要再检测required成员是否初始化 public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName); public required string FirstName { get; init; } public required string LastName { get; init; } }

file 关键字

csharp
// In File1.cs: file class HiddenWidget { public int Work() => 42; } // In File2.cs: 不会引起冲突 public class HiddenWidget { public void RunTask() { // omitted } }

C# 12

.Net 8 以上

主构造函数

csharp
public class BankAccount(string accountID, string owner) { public string AccountID { get; } = accountID; public string Owner { get; } = owner; public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}"; public BankAccount() : this("000", "111") { } }

集合表达式

csharp
int[] row0 = [1, 2, 3]; //终于 int[] row1 = [4, 5, 6]; int[] row2 = [7, 8, 9]; int[] single = [.. row0, .. row1, .. row2]; //展开运算符.. foreach (var element in single) { Console.Write($"{element}, "); } // output: // 1, 2, 3, 4, 5, 6, 7, 8, 9, int[][] twoDFromVariables = [row0, row1, row2];

using 升级

csharp
using Point = (int x, int y); using X = string?; //不行,不能是可为null的引用类型 using Y = int?; //可以

C# 13

新增转义

\e 转义 ESCAPEU+001B,之前使用\u001b

隐式索引访问

csharp
var v = new S() { buffer = { [^1] = 0, //可以使用 ^ 从末尾开始初始化数组 [^2] = 1, [^3] = 2, [^4] = 3, [^5] = 4, [^6] = 5, [^7] = 6, [^8] = 7, [^9] = 8, [^10] = 9 } };

其他

yield

不必实现IEnumerable 亦可实现遍历

c#
public IEnumerator GetEnumerator() { for (int i = 0; i < 10; i++) { if (i < 8)//只遍历前8个 { yield return array[i]; } else { yield break; } } }

友元程序集(Friend Assembly)

可以让其它程序集访问自己的internal成员

c#
[assembly:InternalsVisibleTo("cs_friend_assemblies_2")] public virtual int TestProperty { protected set { } get { return 0; } }

unsafe

可以在其中使用指针类型,需要确保代码不会引发安全风险或指针错误,CLR无法验证unsafe的代码。

volatile

用来表示相关的字可能被多个线程同时访问,编译器不会对相应的值做针对单线程下的优化,保证相关的值在任何时候访问都是最新的。

扩展方法

扩展方法必须被定义在静态类中,并且必须是非泛型、非嵌套的静态类。为某个类的对象添加方法,只在当前命名空间有效。

c#
public static class StrExtra { public static int StrToInt32(this string s) { return Int32.Parse(s); } public static T[] SomeMethd<T>(this T[] source, int pram1, int pram2) { // } } //使用"456".StrToInt32()

表达式树

树形结构,叶子节点为参数或常量,上级节点为表达式体,计算方式从下(叶子)到上(顶点)

Lambd表达式树:Expression<Func<int, int, bool>> fun = (x, y) => x < y;

使用:var f = fun.Compile(); boolval=f(5,6);

其中x,y是参数,x<y是表达式体。

组装表达式树:

c#
ParameterExpression p1 = Expression.Parameter(typeof(int), "x"); ParameterExpression p2 = Expression.Parameter(typeof(int), "y"); BinaryExpression expr = Expression.GreaterThan(p1, p2); Expression<Func<int, int, bool>> fun =Expression.Lambda<Func<int, int, bool>>(expr, p1, p2);

调用方信息 .Net 4.5

使用方法:TraceMessage("Something happened.");

实现方法:

c#
public void TraceMessage(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { Trace.WriteLine("message: " + message); Trace.WriteLine("member name: " + memberName);//输出方法名 Trace.WriteLine("source file path: " + sourceFilePath);//文件名 Trace.WriteLine("source line number: " + sourceLineNumber);//行号 }

字符串嵌入

使用{}代替+

var str = $"FirstName = , LastName=";

nameof

nameof(p): p可以是类,参数,方法等,返回它们的名称

例: nameof(String) 返回"String"

应用场景:在INotifyPropertyChanged时需要OnPropertyChanged("参数名")时可以nameof(参数)使用,避免参数名更改后OnPropertyChanged未更改,不会报错,所以不容易查找的问题

Index initializers ,对字典类型初始化

c#
private Dictionary<int, string> webErrors = new Dictionary<int, string> { [404] = "Page not Found", [302] = "Page moved, but left a forwarding address.", [500] = "The web server can't come out to play today." };

when 筛选器

适用于 catch语句或者 switch case中

  • catch (ExceptionType [e]) when (e.Message.Contains("404"))

  • switch status { case Shape shape when status.Code== 0: break; }