1.default
在C#7.1中引入,表示某个类型默认值的字面量
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 >(); string defaultString = GetDefaultValue<string >();
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.异常过滤器
只会处理满足特定条件的异常,这样可以提高程序的性能并简化异常处理逻辑
1 2 3 4 5 6 7 8 9 try { } catch (Exception ex) when (){ }
5.模式匹配
在c#7.0引入,比如:obj is int number,同时允许增加额外条件
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
代码简洁.支持模式匹配.可匹配范围灵活
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接口的类或结构
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开始更加简洁
1 2 using var writer = new StreamWriter("example.txt" );writer.WriteLine("Hello, Modern World!" );
8.静态局部函数
优势:提高性能
与非静态局部函数的区别:
非静态函数可以访问外部方法中的局部变量和类的实例成员
静态局部函数只能访问外部方法的静态成员和常量
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 ; void NonStaticLocalFunction () { Console.WriteLine(localVariable); } NonStaticLocalFunction(); static void StaticLocalFunction () { Console.WriteLine("This is a static local function" ); } StaticLocalFunction(); }
9.接口与抽象类的区别
最大的不同就是抽象类允许存储状态(成员变量),而接口只允许定义方法或属性。另外接口允许多重继承,而类(包括抽象类)只允许单继承
在 8.0 以前的 C# 版本中,接口类似于只有抽象成员的抽象基类。 实现接口的类或结构必须实现其所有成员。
从 C# 8.0 开始,接口可以定义其部分或全部成员的默认实现。 实现接口的类或结构不一定要实现具有默认实现的成员。 有关详细信息,请参阅默认接口方法 。
接口无法直接进行实例化。 其成员由实现接口的任何类或结构来实现。
一个类或结构可以实现多个接口。 一个类可以继承一个基类,还可实现一个或多个接口。
10.空合并赋值运算符
??=,c#8.0引入,它用于将一个变量设置为某个值,前提是该变量当前为null;如果变量不为null,那么它将会保留当前值,不会被赋值操作改变。它的诊断和赋值行为合并成一个原子操作
优势:
处理缓存或懒加载,第一次访问初始化
线程安全,原子操作
其他相关运算符:
?. : 空对象安全访问运算符
?? : 空合并运算符
11.LINQ
from起手,select或 group by结尾
Linq默认延迟执行,遇到 ToList()、ToArray()、Count() 方法会立即执行查询。
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} 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}; var query = from student in students let fullName = student.FirstName + " " + student.LastName where fullName.Lenth > 10 select fullName; var query = students.Select(s => s.Name).Distinct();var query = students.OrderBy(s => s.Age) .ThenBy(s => s.Name); var query = students.Agreegate(0 , (sum, student) => sum + student.Age);TakeWhile:从集合中提取元素,直到条件不满足为止。 SkipWhile:跳过元素,直到条件不满足为止。 var query = list1.Concat(list2);表达式树 + 委托 Expression<Func<Person,bool >> var query = students.GroupBy(s => s.Department) .Select(g => new {Department = g.Key(), Count = g.Count()}); IEnumable<IGrouping<TKey,TSource>> var query = list1.Zip(list2, (a, b) => a + b);var query = list1.Union(list2);var query = listA.Except(listB);var query = list1.Intersect(list2);var query = students.DefaultEmpty(new Student {Name = "Default" });public IEnumerable<Employee> GetSubordinates (Employee manager ){ var subordinates = employees.Where(e => e.ManagerId == manager.Id); return subordinates.Concat(subordinates.SelectMany(e => GetSubordinates(e))); } 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());var query = students.ToDictionary(s => s.Id, s => s.Name);var enumerable = students.AsEnumerable(); var queryable = students.AsQueryable();
12.索引和范围操作符
在c#8.0之后引入,c#8.0还引入了异步流(Async Streams).默认接口方法
索引操作符(^):用来从集合的末尾开始计数;^1表示最后一个元素,^2表示倒数第二个元素
范围操作符(..):用来指定一个范围。它可以用于获取子数组或子字符串。(1..3)表示获取下标 >= 1, <3的元素
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 ]; int [] subArray = numbers[1. .3 ]; int [] firstThree = numbers[..3 ]; int [] lastTwo = numbers[3. .]; string text = "Hello, World!" ;string subText = text[^6. .^1 ];
原理:可以应用于实现了System.Index和System.Range的类型;它们都是一个结构体
13.操作符重载 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); } public override bool Equals (object obj ) { if (obj is Point) { Point p = (Point)obj; return this == p; } return false ; } public override int GetHashCode () { return X.GetHashCode() ^ Y.GetHashCode(); } }
14.record
c#9.0中引入,它是一种特殊的引用类型,用于不可变的数据建模。主要特征是内置了值相等属性,意味着如果两个record的所有属性值都相同,那么这两个record的实例就是相等的。
record支持继承
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" );Console.WriteLine(person1 == person2);
优势
比如当你有一个函数需要修改对象的一个值属性时,你可以直接使用with表达式来生成一个新的对象,而无需整体复制所有属性。
1 2 var person3 = person1 with { LastName = "Smith" };Console.WriteLine(person3);
向不可变性.函数式编程靠拢,类的实例可以随意修改,容易导致数据不一致的问题,定义在大括号中的属性可变
15.init属性
c#9.0引入,允许运行时只读的属性通过init关键字来初始化。
替代set构造,相当于第一次赋值之后后面都不能修改。在对象构建阶段设置属性的值,在对象创建之后这些值不能被修改
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 };
与readonly比较 readonly适用于字段,使字段在初始化后只有读的权限;而init则是针对属性的,可以在对象初始化时设置值,但之后无法修改
与Record结合 记录类型是不可变的引用类型,默认情况下所有的属性都是用init关键字初始化的
1 2 3 4 5 public record Car { public string Model { get ; init ; } public int Year { get ; init ; } }
16.扩展方法
在c#中,扩展方法是必须定义在静态类中的,这是因为扩展方法是静态方法,它们通过第一个参数的this关键字来指定要扩展的类型
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中引入,一般在某个项目的公共文件统一声明
1 2 3 global using System;global using MyProject.Common;
扩展
减少冗余,提高代码简洁性
不会对运行时性能产生影响
18.反射
在程序运行时动态获取对象的方法或属性
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 ) { Type myClassType = typeof (MyClass); object myClassInstance = Activator.CreateInstance(myClassType); 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)
自定义不可变类
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.多播委托
通过+或+=运算符将多个方法附加到一个多播委托上,使用-或-=移除一个方法
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; notify.Invoke(); } public static void Method1 () { Console.WriteLine("Method1 called" ); } public static void Method2 () { Console.WriteLine("Method2 called" ); } }
扩展
多播委托的返回值不是void,将会返回最后一个被调用方法的返回值
如果有一个方法抛出异常,后续的方法将不会被调用