1.default

在C#7.1中引入,表示某个类型默认值的字面量

c#
1
2
3
4
5
6
7
8
9
10
11
int a = default; ==> int a = 0;
bool b = default; ==> bool b = false;

public T GetDefaultValue<T>()
{
return default;
}

// 调用时将自动匹配默认值
int defaultInt = GetDefaultValue<int>(); // 等价于 0
string defaultString = GetDefaultValue<string>(); // 等价于 null

2.静态导入

使用using static,如 using static NamespaceName.ClassName;

允许在不指定类名的情况下直接使用静态类的成员;

虽然静态导入可以简化代码,但它并不会对内存管理产生影响;

3.泛型约束

  • where T : struct -约束T为值类型
  • where T : class -约束T为引用类型
  • where T : new() -约束T必须有无参构造函数
  • where T : BaseClass -约束T必须继承于BaseClass
  • where T : IBaseInterface -约束T必须实现接口IBaseInterface

4.异常过滤器

只会处理满足特定条件的异常,这样可以提高程序的性能并简化异常处理逻辑

c#
1
2
3
4
5
6
7
8
9
try
{
// 你的代码
}
catch (Exception ex) when (/* 条件表达式 */)
{
// 处理异常的代码
}

5.模式匹配

在c#7.0引入,比如:obj is int number,同时允许增加额外条件

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
object obj = 42;

if (obj is int number)
{
Console.WriteLine($"Integer: {number}");
}
else if (obj is string str)
{
Console.WriteLine($"String: {str}");
}
else
{
Console.WriteLine("Unknown type");
}


object obj = 42;
if (obj is int number && number > 40)
{
Console.WriteLine($"Integer greater than 40: {number}");
}

//定义元组
(Object, Object) tuple = (42, "cc");

6.switch

代码简洁.支持模式匹配.可匹配范围灵活

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int number = 1;
string result = number switch
{
1 => "One",
2 => "Two",
_ => "Other"
};

//模式匹配
Object obj = 42;
string description = obj switch {
int i => $"Integer: {i}",
string s => $"String: {s}",
_ => "Unknown type"
};

//范围匹配
int score = 85;
string grade = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};

7.使用using

using声明通常用于确保对象在使用后被正确地释放,它适用于那些实现了IDisposable接口的类或结构

c#
1
2
3
4
5
//举例
using (StreamWriter writer = new StreamWriter("example.txt"))
{
writer.WriteLine("Hello, World!");
}
工作原理

using声明实际上会在底层被转换为try-finally结构。在try块中是使用的资源,而在finally块中则会调用资源的Dispose方法。这个转换机制确保了资源在使用完后总会被正确释放。

在c#8.0开始更加简洁

c#
1
2
using var writer = new StreamWriter("example.txt");
writer.WriteLine("Hello, Modern World!");

8.静态局部函数

优势:提高性能

与非静态局部函数的区别:

  • 非静态函数可以访问外部方法中的局部变量和类的实例成员
  • 静态局部函数只能访问外部方法的静态成员和常量
c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void OuterMethod()
{
int localVariable = 10;

// 非静态局部函数,可以访问localVariable
void NonStaticLocalFunction()
{
Console.WriteLine(localVariable);
}
NonStaticLocalFunction();

// 静态局部函数,不能访问localVariable
static void StaticLocalFunction()
{
// Console.WriteLine(localVariable); // 编译错误
Console.WriteLine("This is a static local function");
}
StaticLocalFunction();
}

9.接口与抽象类的区别

最大的不同就是抽象类允许存储状态(成员变量),而接口只允许定义方法或属性。另外接口允许多重继承,而类(包括抽象类)只允许单继承

  • 在 8.0 以前的 C# 版本中,接口类似于只有抽象成员的抽象基类。 实现接口的类或结构必须实现其所有成员。
  • 从 C# 8.0 开始,接口可以定义其部分或全部成员的默认实现。 实现接口的类或结构不一定要实现具有默认实现的成员。 有关详细信息,请参阅默认接口方法
  • 接口无法直接进行实例化。 其成员由实现接口的任何类或结构来实现。
  • 一个类或结构可以实现多个接口。 一个类可以继承一个基类,还可实现一个或多个接口。

10.空合并赋值运算符

??=,c#8.0引入,它用于将一个变量设置为某个值,前提是该变量当前为null;如果变量不为null,那么它将会保留当前值,不会被赋值操作改变。它的诊断和赋值行为合并成一个原子操作

c#
1
variable ??= value;

优势:

  • 处理缓存或懒加载,第一次访问初始化
  • 线程安全,原子操作

其他相关运算符:

  • ?. : 空对象安全访问运算符
  • ?? : 空合并运算符

11.LINQ

from起手,select或 group by结尾

Linq默认延迟执行,遇到 ToList()ToArray()Count() 方法会立即执行查询。

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
var products = from product in dbContext.Products
where product.StockCount > 0
orderby product.Price descending
select new { product.Name, product.Price}

//Parallel多线程
var arr = Enumerable
.Range(1,10)
.ToArray()
.AsParallel()
.Select(x => x * x);

//笛卡尔积
var prods =
from i in Enumerable.Range(0, 5)
from j in Enumerable.Range(0, 4)
from k in Enumerable.Range(0, 3)
select $"{i}, {j}, {k}";


var prods = Enumerable
.Range(0,5)
.SelectMany(r => Enumerable.Range(0, 4), (l, r) => (l, r))
.SelectMany(r => Enumerable.Range(0, 3), (l, r) => (l.l, l.r, r))
.Select(x => x.ToString());

//字母频率
var words = new string[] {"tom", "jerry", "spike", "tyke", "butch", "quacker"};

var query =
from w in words
from c in w
group c by c into g
select new {g.key, Count = g.Count()} into a
orderby a.Count descending
select a;

var query = words
.SelectMany(c => c)
.GroupBy(c => c)
.Select(g => new {g.Key, Count = g.Count()})
.OrderByDescending(g => g.Count);

//联表查询
var query = from c in categories
join p in products on c.Id equals p.CategoryId
select new { c.Name, p.Name };


//多条件联表查询
var query = from c in categories
join p in products on new {c.Id, c.Remark} equals new {p.CategoryId, p.Remark}
select new {c.Name, p.Name};

//let的使用
var query = from student in students
let fullName = student.FirstName + " " + student.LastName
where fullName.Lenth > 10
select fullName;

//Distinct
var query = students.Select(s => s.Name).Distinct();


//多条件排序
var query = students.OrderBy(s => s.Age)
.ThenBy(s => s.Name);


//Aggregate 累加求和
var query = students.Agreegate(0, (sum, student) => sum + student.Age);


//LINQ 中的 TakeWhile 和 SkipWhile 有什么区别?
TakeWhile:从集合中提取元素,直到条件不满足为止。
SkipWhile:跳过元素,直到条件不满足为止。


//Concat 用于将两个集合合并为一个集合
var query = list1.Concat(list2);


//动态查询
表达式树 + 委托 Expression<Func<Person,bool>>


//实现linq查询的分组统计
var query = students.GroupBy(s => s.Department)
.Select(g => new {Department = g.Key(), Count = g.Count()});


//GroupBy的返回值
IEnumable<IGrouping<TKey,TSource>>



//zip 用于将两个元素按顺序配对
var query = list1.Zip(list2, (a, b) => a + b);


// Union 联合查询
var query = list1.Union(list2);


//Except 在A集合存在,B集合不存在
var query = listA.Except(listB);


//Intersect 实现两个集合的交集

var query = list1.Intersect(list2);


//DefaultEmpty 用于在集合为空时返回一个默认值

var query = students.DefaultEmpty(new Student {Name = "Default"});


//实现Linq的递归查询

public IEnumerable<Employee> GetSubordinates(Employee manager)
{
var subordinates = employees.Where(e => e.ManagerId == manager.Id);
return subordinates.Concat(subordinates.SelectMany(e => GetSubordinates(e)));
}


//IEqualityComparer 自定义比较器

public class StudentComparer : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y) => x.Id == y.Id;
public int GetHashCode(Student obj) => obj.Id.GetHashCode();
}

var distinctStudents = students.Distinct(new StudentComparer());


//ToDictionary
var query = students.ToDictionary(s => s.Id, s => s.Name);


//AsEnumerable 和 AsQueryable
var enumerable = students.AsEnumerable(); // 内存中查询
var queryable = students.AsQueryable(); // 数据库查询



12.索引和范围操作符

在c#8.0之后引入,c#8.0还引入了异步流(Async Streams).默认接口方法

索引操作符(^):用来从集合的末尾开始计数;^1表示最后一个元素,^2表示倒数第二个元素

范围操作符(..):用来指定一个范围。它可以用于获取子数组或子字符串。(1..3)表示获取下标 >= 1, <3的元素

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义一个数组
int[] numbers = { 1, 2, 3, 4, 5 };

// 使用索引操作符获取最后一个元素
int lastNumber = numbers[^1]; // 输出 5

// 使用范围操作符获取子数组
int[] subArray = numbers[1..3]; // 输出 [2, 3]

// 使用范围操作符从开始到某索引
int[] firstThree = numbers[..3]; // 输出 [1, 2, 3]

// 使用范围操作符从某索引到结束
int[] lastTwo = numbers[3..]; // 输出 [4, 5]


string text = "Hello, World!";
string subText = text[^6..^1]; // "World"

原理:可以应用于实现了System.Index和System.Range的类型;它们都是一个结构体

13.操作符重载

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public struct Point
{
public int X { get; }
public int Y { get; }

public Point(int x, int y)
{
X = x;
Y = y;
}

// 重载 + 操作符
public static Point operator +(Point a, Point b)
{
return new Point(a.X + b.X, a.Y + b.Y);
}

// 重载 == 操作符
public static bool operator ==(Point a, Point b)
{
return a.X == b.X && a.Y == b.Y;
}

// 重载 != 操作符
public static bool operator !=(Point a, Point b)
{
return !(a == b);
}

// 覆写 Equals 方法
public override bool Equals(object obj)
{
if (obj is Point)
{
Point p = (Point)obj;
return this == p;
}
return false;
}

// 覆写 GetHashCode 方法
public override int GetHashCode()
{
return X.GetHashCode() ^ Y.GetHashCode();
}
}

14.record

c#9.0中引入,它是一种特殊的引用类型,用于不可变的数据建模。主要特征是内置了值相等属性,意味着如果两个record的所有属性值都相同,那么这两个record的实例就是相等的。

record支持继承

c#
1
2
3
4
5
6
7
8
public record Person(string FirstName, string LastName);

var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");

// person1 和 person2 是相等的,因为它们的属性值相同
Console.WriteLine(person1 == person2); // 输出 True

优势

  • 装饰器模式替代

比如当你有一个函数需要修改对象的一个值属性时,你可以直接使用with表达式来生成一个新的对象,而无需整体复制所有属性。

c#
1
2
var person3 = person1 with { LastName = "Smith" };
Console.WriteLine(person3); // 输出: Person { FirstName = John, LastName = Smith }
  • 向不可变性.函数式编程靠拢,类的实例可以随意修改,容易导致数据不一致的问题,定义在大括号中的属性可变

15.init属性

c#9.0引入,允许运行时只读的属性通过init关键字来初始化。

替代set构造,相当于第一次赋值之后后面都不能修改。在对象构建阶段设置属性的值,在对象创建之后这些值不能被修改

c#
1
2
3
4
5
6
7
8
9
10
public class Person
{
public string Name { get; init; }
public int Age { get; init; }
}

var person = new Person { Name = "Alice", Age = 30 };

// 这样尝试修改属性值会导致编译错误
// person.Name = "Bob";

与readonly比较

readonly适用于字段,使字段在初始化后只有读的权限;而init则是针对属性的,可以在对象初始化时设置值,但之后无法修改

与Record结合

记录类型是不可变的引用类型,默认情况下所有的属性都是用init关键字初始化的

c#
1
2
3
4
5
public record Car
{
public string Model { get; init; }
public int Year { get; init; }
}

16.扩展方法

在c#中,扩展方法是必须定义在静态类中的,这是因为扩展方法是静态方法,它们通过第一个参数的this关键字来指定要扩展的类型

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class MyExtensions
{
public static void Print(this string str)
{
Console.WriteLine(str);
}
}


using MyNamespace;

string example = "Hello, World!";
example.Print(); // 调用扩展方法

17.全局命名空间

在c#10中引入,一般在某个项目的公共文件统一声明

c#
1
2
3
global using System;
global using MyProject.Common;

扩展

  • 减少冗余,提高代码简洁性
  • 不会对运行时性能产生影响

18.反射

在程序运行时动态获取对象的方法或属性

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;

namespace ReflectionDemo
{
public class MyClass
{
public void Greet()
{
Console.WriteLine("Hello,World");
}
}

Class Program
{
static void main(string[] args)
{
//step1
Type myClassType = typeof(MyClass);

//step2
object myClassInstance = Activator.CreateInstance(myClassType);

//step3
MyClass myClass = (MyClass)myClassInstance;
myClass.Greet();


//访问属性和方法
MethodInfo method = myClassType.GetMethod("Greet");
method.Invoke(myClassInstance, null);

}
}

}

19.不可变类型.可变类型

不可变

int.double.string,一些自定义的不可变的类(通常用readonly)修饰

线程安全.效率低(频繁创建新的实例会增加内存开销和垃圾回收负担)

可变

比如List.Array.StringBuilder

灵活性高.并发问题

c#7引入了更多的不可变数据结构,比如元组(tuple)和具名元组(ValueTuple)

自定义不可变类

c#
1
2
3
4
5
6
7
8
9
10
public class ImmutablePoint {
public int X { get; }
public int Y { get; }

public ImmutablePoint(int x, int y) {
X = x;
Y = y;
}
}

20.多播委托

通过+或+=运算符将多个方法附加到一个多播委托上,使用-或-=移除一个方法

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 定义一个委托类型
public delegate void Notify();

public class Program
{
public static void Main()
{
// 创建委托实例并附加方法
Notify notify = Method1;
notify += Method2;

// 调用多播委托,将依次调用 Method1 和 Method2
notify.Invoke();
}

public static void Method1()
{
Console.WriteLine("Method1 called");
}

public static void Method2()
{
Console.WriteLine("Method2 called");
}
}

扩展

  • 多播委托的返回值不是void,将会返回最后一个被调用方法的返回值
  • 如果有一个方法抛出异常,后续的方法将不会被调用