C#
编程规范
命名规则
- 编程符必须以字母或下划线开头
- 标识符可以包含Unicode字符、十进制数字字符、Unicode连接字符、Unicode组合字符或Unicode格式字符
可以在标识符上使用@前缀声明与C#关键字匹配的标识符。eg.@if声明为if的标识符
命名约定
类型名称、命名空间和所有公共成员使用PascalCase
- 接口名称以I开头,属性类型以Attribute结尾,对变量、方法和类使用有意义的描述名称
- 枚举类型对非标记使用单数名词,对标记使用复数名词,标识符不应包含两个连续的下划线_字符,这些名称保留給编译器生成
- 清晰>简洁,类名和方法名称采用PascalCase,常量名包括字段和局部变量也是
- 对方法参数、局部变量使用驼峰式大小写,专用实例字段以下划线_开头,其余文本为驼峰式大小写,静态字段以s_开头
- 避免在名称中使用缩写或首字母,广为人知的除外,使用遵循反向域名表示法的命名空间
- S用于结构,C用于类,M用于方法,v用于变量,p用于参数,r用于ref参数
编码约定
字符串
- 使用内插连接短字符串
string displayName = $"{first}, {second}";
- 循环追加字符串,尤其是大量文本,使用stringbuilder
var a = "aaaaaaaaaaaaaaaaaaa";
var sb = new StringBuilder();
foreach (var i in a)
{sb.Append(i);
}
数组
- 声明行上初始化数组时,使用简介的语法
// 不能使用var
int[] v1 = {1, 2, 3, 4, 5};
// 显式
var v2 = new string[]{"a, b, c, d"};
委托
使用Func<>和Action<>,而不是委托在类中定义委托方法
Action<string> a1 = x => Console.WriteLine($"x is:{x}");
Action<string, string> a2 = (x, y) => Console.WriteLine($"x is:{x}, y is {y}");Func<string, int> f1 = x => Convert.ToInt32(x);
Func<int, int, int> f2 = (x, y) => x + y;// 使用Func<> 或 Action<>委托定义的签名调用方法
a1("string for x");
a2("string for x", "string for y");
Console.WriteLine($"The value is {f1("1")}");
Console.WriteLine($"The sum is {f2(1, 2)}");
多播委托
// 委托是一种声明,类似于一个接口,带有关键字,后面跟着返回类型和参数列表
public delegate void MyDelegate(string message);public class Program
{public static void Main(){MyDelegate printDelegate = new MyDelegate(PrintMessage);printDelegate += PrintUpperCase;printDelegate("Hello World");}public static void PrintMessage(string message){Console.WriteLine(message);}public static void PrintUpperCase(string message){Console.WriteLine(message.ToUpper());}
}
释放资源
// 显示释放资源
Font bodyStyle = new Font("Arial", 10.0f);
try
{byte charset = bodyStyle.GdiCharSet;
}
finally
{if (bodyStyle != null){((IDisposable)bodyStyle).Dispose();}
}
// 使用using,在离开作用域时自动调用Dispose方法,即使出现异常任然可以释放掉资源
using Font normalStyle = new Font("Arial", 10.0f);
byte charset3 = normalStyle.GdiCharSet;
new关键字
// 创建对象时下列两种方法等效
var user = new User();
User user2 = new();
索引
^类似于取反,..类似于python中的:,可以切片。String、Span 和 ReadOnlySpan。List 支持索引,但不支持范围。
在数组中获取范围是从初始数组复制的数组而不是引用的数组,修改生成的值不会更改数组中的值
string[] words = [// index from start index from end"The", // 0 ^9"quick", // 1 ^8"brown", // 2 ^7
];
// brown
Console.WriteLine(words[^1]);
// uick
Console.WriteLine(words[1][1..]);
// ui
Console.WriteLine(words[1][1..3]);
// brown
Console.WriteLine(words[words.Length - 1]);
Base
类比理解为Java中的extend和implement,具有几个不同的用途,都与继承有关
// 调用基类构造函数
public class DerivedClass: BaseClass{public DerivedClass(): base(){} // 调用基类构造函数public DerivedClass(): base(value){} // 调用且传参
}// 访问基类成员
public class BaseClass{public void PrintMessage() {CW("...");}
}public class Derived: BaseClass{public void PrintMessage() {CW("...");base.PrintMessage(); // 调用基类PrintMessage方法}
}// 指定基类作为强制转换目标
public class BaseClass{}public class Derived: BaseClass{public void Method() {BaseClass bc = base; // 将派生类对象转换为基类类型}
}
?.条件运算符
类似Java里面的optional,左侧为null则表达式为null,不为null则走后续流程,避免空指针
if pwd == auth?.pwd
??合并运算符
类似Python的海象符,如果左边为null则返回右边的数
string result = str ?? "default";
Using()
Using是资源管理语句,常用于需要显式释放的非托管资源,如文件流、网络连接、数据库连接等。
- 当
using
块内的代码执行完毕时,无论是正常完成还是因为异常而退出,using
语句都会自动调用每个对象的Dispose
方法。这样可以确保释放
// base64编码的字符串转换成字节数组
using(var mStream = new MemoryStream(Convert.FromBase64String(source)));// 使用解密器对数据解密
using (var cryptoStream = new CryptoStream(mStream,DesHandler.CreateDecryptor(DesHandler.Key, DesHandler.IV), CryptoStreamMode.Read));// 创建对象读取解密后的文本数据
using (var reader = new StreamReader(cryptoStream));
readonly
类比Java中的finall,初始化后不能修改
- 不能用于修饰静态字段、修饰类或接口
- 字段或局部变量使用使用了readonly则不需要const
// 字段,必须在声明时或构造函数中初始化,不能修改
public readonly int Field;// 变量,必须在声明时初始化,不能修改
public void Func() {readonly int local = 10;
}// 方法,不能包含任何可变参数,不可抛出异常
public readonly void MyMethod() {// pass
}// 委托,不能更改
public readonly Action MyDelegate;// 属性,get访问器必须返回一个常量,而set访问器不能被实现
public readonly int Property{get; private set;}
out关键字
用于声明的一个输出参数,方法调用时通过参数通过引用传递,如果使用out参数则必须在方法体内为每个out参数赋值。我的理解是,就是将带有out关键字的参数return出去,
public void Example(int a, out int b) {b = a * 2;
}
int result;
Example(5, out result); // result = 10
匿名类型
将只读数据封装到单个对象中,而无需显示定义一个类型
var v = new {Amount = 108, Message = 'Hello'};
通常用在查询表达式的select子句中(LINQ),其用来初始化的属性不能为null、匿名函数或指针类型。匿名类型是class类型,直接派生自object,且无法强制转换为除object外的任何类型,支持采用with表达式形式的非破坏性修改,类似于Java中的Set不过只能修改已存在的属性。匿名类型会重写ToString方法
var apple = new {Item = "apple", Price = 1.35};
var onSale = apple with {Price=0.79};
Console.WriteLine(apple);
Console.WriteLine(onSale);
record 记录关键字
类似Java中的final,默认实现不可变性(不可更改对象的任何属性或字段值)、Equals和GetHashCode方法及ToString方法,自动提供了构造函数、属性的比较和字符串表示的功能
- 值相等性:record 类型自动拥有一个经过优化的
Equals
方法,该方法比较两个 record 实例的字段值是否相等。 - 简洁的声明式语法:record 允许使用更简洁的语法来声明只包含数据的类。
- 不可变性:record 类型的实例一旦创建,其状态就不能更改(除非显式地使用可变记录)。
- 继承:record 可以继承自其他 record 或 class,并且可以添加额外的成员。
以下情况考虑使用记录:
- 定义依赖值相等性的数据模型
- 定义对象不可变类型
关系模式
string WaterState(int temp) => temp switch{(>32) and (<212) => "liquid",< 32 => "solid",> 212 => "gas",32 => "solid/liquid transition",212 => "liquid/gas transition",_ => throw new ArgumentOutOfRangeException()};
判空
str
string.IsNullOrEmpty(s); // 如果是空串判不出来
string.IsNullOrWhiteSpace(s); // 空串判的出来
List
if (myList == null || !myList.Any()){} // 集合为null或没有元素
if (myList?.Any() == false){} // 集合为null或没有元素
对象
if (obj == null) {} // 对象为空
if (obj is null) {} // 对象为空Object.ReferenceEquals(obj1, obj2); // 判断对象引用地址是否一样
object.Equals(str1, str2); // 判断两对象值是否相等,需要重写Equals方法
object DefaultObj = obj ?? new object(); // 使用??运算符提供默认值
值引用
int? myInt = null;
if (!myInt.HasValue) {}
int defaultInt = myInt ?? 0; // 使用??运算符提供默认值
主构造函数
- 初始化属性
// 只读情况下
public readonly struct Distance(double dx, double dy)
{public readonly double Magnitude { get; } = Math.Sqrt(dx * dx + dy * dy);public readonly double Direction { get; } = Math.Atan2(dy, dx);
}// 两种方法初始化对象效果一致
public readonly struct Distance
{public readonly double Magnitude { get; }public readonly double Direction { get; }public Distance(double dx, double dy){Magnitude = Math.Sqrt(dx * dx + dy * dy);Direction = Math.Atan2(dy, dx);}
}// 非只读情况下,
public struct Distance(double dx, double dy)
{public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);public readonly double Direction => Math.Atan2(dy, dx);public void Translate(double deltaX, double deltaY){dx += deltaX;dy += deltaY;}public Distance() : this(0,0) { }
}
LINQ
Where
从数据源中筛选出元素
from city in cities where city.Pop is < 200 and >100 select city;
排序
OrderBy
可按升序或降序排列,例子中以Area为主,population为辅
var orderedEnumerable = from country in countries orderby country.Area, country.Population descending select country;
ThenBy
按升序执行次要排序
Reverse
反转集合中的元素
Join
将数据源中元素于另一个数据源元素进行关联和/或合并,连接序列之后必须使用select或group语句指定存储在输入序列中的元素。示例关联Category属性与categories字符串数组中一个类别匹配的prod对象
var cateQuery = from cat in categoriesjoin prod in products on cat equals prod.Categoryselect new{Category = cat,Name = prod.Name};
Let
使用let将结果存储在新范围变量中
from name in names select names let firstName = name.Split(" ")[0]select firstName;
多查询
var query = from student in students// 按照student.Year分组group student by student.Year// 为分组定义别名,后续使用别名进行分组into studentGroupselect new{ // 每个分组的键,学生的年级Level = studentGroup.Key,// 每个分组中,所有学生的平均成绩HighestScore = (from student2 in studentGroup select student2.ExamScores.Average()).Max()};
查询对象
var entity = from o in InComingOrderswhere o.OrderSize > 5select new Customer { Name = o.Name, Phone = o.Phone };
// LINQ写法
var entity2 = InComingOrders.Where(e => e.OrderSize > 5).Select(e => new Customer { Name = e.Name, Phone = e.Phone });
作为数据表达式(Lambda)
结合out关键字返回查询
void QueryMethod(int[] ints, out List<string> returnQ) =>returnQ = (from i in ints where i < 4 select i.ToString()).ToList();int[] nums = [0, 1, 2, 3, 4, 5, 6, 7];
QueryMethod(nums, out List<string> result);
foreach (var item in result)
{Console.WriteLine(item);
}
eg
// 普通方法编写查询总分数据
var studentQuery1 = from student in studnetslet totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3]select totalScore;
// 使用Linq方法编写查询总分数据
var studentQuery2 = studnets.Select(e => e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3]);
// 统计平均分
double average = studentQuery1.Average();// 将大于平均分的学生数据映射为对象
var query1 =from student in studnetslet x = student.Scores[0] + student.Scores[1] +student.Scores[2] + student.Scores[3]where x > averageselect new { id = student.ID, score = x };
// 使用Linq写法
var query2 = studnets.Where(e => e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] > average).Select(e =>new { id = e.ID, score = e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] });
// Linq简洁写法
var query3 = studnets.Select(e => new { id = e.ID, score = e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] }).Where(e => e.score > average);foreach (var item in query1)
{Console.WriteLine("Student ID: {0},Score: {1}", item.id, item.score);
}
投影运算
SelectMany
多个from子句投影字符串列表中每个字符串中的单词
List<string> phrases = ["an apple a day", "the quick brown fox"];
// 普通写法
var query = from phrase in phrases from word in phrase.Split(' ') select word;
// Linq写法
var query2 = phrases.SelectMany(e => e.Split(' '));
Zip列表压缩元组,类似python
// An int array with 7 elements.
IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7];
// A char array with 6 elements.
IEnumerable<char> letters = ['A', 'B', 'C', 'D', 'E', 'F'];foreach (var (first, second, third) in numbers.Zip(letters, emoji))
{Console.WriteLine($"Number:{first} is zipped with letter: {second} and emoji {third}");
}
Set集合操作
去重
string[] words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"];
// 去重
var query = from word in words.Distinct() select word;
// 根据条件去重
var query2 = from word in words.DistinctBy(e => e.Length)select word;
差集
string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// console:queik、brown、fox,输出1在2中没有的元素
IEnumerable<string> query = from word in words1.Except(words2) select word;
// expectBy同理,根据自定义字段进行操作
var result = new List<Person> { new Person { Name = "Alice" }, new Person { Name = "Bob" } }.ExceptBy(person => person.Name,new List<Person> { new Person { Name = "Alice" }, new Person { Name = "Charlie" } });
// result 将包含 { new Person { Name = "Bob" } },因为 "Alice" 在两个集合中都存在,而 "Bob" 和 "Charlie" 只在第一个集合中。
交集
string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// 输出the
IEnumerable<string> query = from word in words1.Intersect(words2) select word;
var list = words1.Intersect(words2).Select(e => e.ToUpper()).ToList();
// 通过比较名称生成 Teacher 和 Student 的交集
(Student person instudents.IntersectBy(teachers.Select(t => (t.First, t.Last)), s => (s.FirstName, s.LastName)))
并集
string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// 使用UnionBy
(var person instudents.Select(s => (s.FirstName, s.LastName)).UnionBy(teachers.Select(t => (FirstName: t.First, LastName: t.Last)), s => (s.FirstName, s.LastName)))
// 输出:the quick brown fox jumped over lazy dog
var query = (from word in words1.Union(words2) select word).ToList();
var list = words1.Union(words2).ToList();
限定符
- All():所有
- Any():任何
- Contains():正好
IEnumerable<string> names = from student in studentswhere student.Scores.Contains(95)select $"{student.FirstName} {student.LastName}: {string.Join(", ", student.Scores.Select(s => s.ToString()))}";
- Skip():跳过序列中指定位置之前的元素
- SkipWhile():基于谓词函数跳过元素,直到元素不符合条件
- Take():获取序列中指定位置之前的元素
- TakeWhile():同上操作
- Chunk():将序列元素拆分为指定最大大小的区块
var resource = Enumerable.Range(0, 8);
// 012
foreach (var i in resource.Take(3)){ }
// 345678
foreach (var i in resource.Skip(3)){ }
// 012345
foreach (var i in resource.TakeWhile(e=>e<5)){ }
// 678
foreach (var i in resource.SkipWhile(e=>e<5)){ }
// 平均分块,将数据分成三块,123、456、78
int chunkNum = 1;
foreach (int[] chunk in Enumerable.Range(0, 8).Chunk(3))
{Console.WriteLine($"Chunk {chunkNum++}:)");foreach (int item in chunk){Console.WriteLine($" {item}");}Console.WriteLine();
}
数据类型转换
- AsEnumerable - 返回类型转化为IEnumerable
的输入 - AsQueryable - 泛型IEnumerable转换为泛型IQueryable
- Cast - 集合中的元素转换为指定类型
- OfType - 转换为指定类型的能力筛选值
- ToArray - 集合转换为数组(强制执行查询)
- ToDictionary - 根据键选择器函数将元素放入 Dictionary。 此方法强制执行查询
- ToList - 集合转换为List
- ToLookUp - 根据键选择器函数将元素放入 Lookup(一对多字典,强制执行查询)
连表
- Join - 根据键选择函数Join两个序列并提取对 - join...in...on...equals
- GroupJoin - 根据键选择器函数Join两个序列,并对每个元素的结果匹配项分组 - join...in...on...equals...into...
单键
Teacher和Department匹配,TeacherId与该Teacher相匹配
var query = from department in departmentsjoin teacher in teachers on department.TeacherID equals teacher.IDselect new{DepartmentName = department.Name,TeacherName = $"{teacher.First} {teacher.Last}"};
// Linq
var query = teachers// 主表连接副表,parameter2、3是查询条件.Join(departments, teacher => teacher.ID, department => department.TeacherID,// lambda表达式,定义连接结果,创建匿名类型对象(teacher, department) =>new { DepartmentName = department.Name, TeacherName = $"{teacher.First} {teacher.Last}" });
组合键
IEnumerable<string> query =from teacher in teachersjoin student in students on new{FirstName = teacher.First,LastName = teacher.Last} equals new{student.FirstName,student.LastName}select teacher.First + " " + teacher.Last;// Linq写法
IEnumerable<string> query = teachers.Join(students,teacher => new { FirstName = teacher.First, LastName = teacher.Last },student => new { student.FirstName, student.LastName },(teacher, student) => $"{teacher.First} {teacher.Last}");
多联结
var query = from student in studentsjoin department in departments on student.DepartmentID equals department.IDjoin teacher in teachers on department.TeacherID equals teacher.IDselect new {StudentName = $"{student.FirstName} {student.LastName}",DepartmentName = department.Name,TeacherName = $"{teacher.First} {teacher.Last}"};// Linq
var query = students.Join(departments, student => student.DepartmentID, department => department.ID,(student, department) => new { student, department }).Join(teachers, commonDepartment => commonDepartment.department.TeacherID, teacher => teacher.ID,(commonDepartment, teacher) => new{StudentName = $"{commonDepartment.student.FirstName} {commonDepartment.student.LastName}",DepartmentName = commonDepartment.department.Name,TeacherName = $"{teacher.First} {teacher.Last}"});
分组
- GroupBy - 对共享通用属性进行分组 - group...by
- ToLookup - 将元素插入基于键选择器函数的Lookup(一对多字典)
demo
List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];
IEnumerable<IGrouping<int, int>> groupBy1 = from number in numbers group number by number % 2;
// Linq
IEnumerable<IGrouping<int, int>> groupBy2 = numbers.GroupBy(e => e % 2);foreach (var i in groupBy1)
{Console.WriteLine(i.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");foreach (var i1 in i){Console.WriteLine(i1);}
}
值分组
var groupByFirstLetterQuery =from student in studentslet firstLetter = student.LastName[0]group student by firstLetter;
// Linq
var groupByFirstLetterQuery = students.GroupBy(student => student.LastName[0]);
范围分组
static int GetPercentile(Student s)
{double avg = s.Scores.Average();return avg > 0 ? (int)avg / 10 : 0;
}var groupByPercentileQuery =from student in studentslet percentile = GetPercentile(student)group new{student.FirstName,student.LastName} by percentile into percentGrouporderby percentGroup.Keyselect percentGroup;
// Linq
var groupByPercentileQuery = students.Select(student => new { student, percentile = GetPercentile(student) }).GroupBy(student => student.percentile).Select(percentGroup => new{percentGroup.Key,Students = percentGroup.Select(s => new { s.student.FirstName, s.student.LastName })}).OrderBy(percentGroup => percentGroup.Key);
比较分组
// 匿名类型中的属性将成为Key成员的属性
var groupByHighAverageQuery =from student in studentsgroup new{student.FirstName,student.LastName} by student.Scores.Average() > 75 into studentGroupselect studentGroup;
// Linq
var groupByHighAverageQuery = students.GroupBy(student => student.Scores.Average() > 75).Select(group => new{group.Key,Students = group.AsEnumerable().Select(s => new { s.FirstName, s.LastName })});
按匿名类型分组
// 第一个键值是首字母,第二个键值是布尔值,
//指定该学生再第一次考试中额得分是否超过85
var groupByCompoundKey =from student in studentsgroup student by new{FirstLetterOfLastName = student.LastName[0],IsScoreOver85 = student.Scores[0] > 85} into studentGrouporderby studentGroup.Key.FirstLetterOfLastNameselect studentGroup;
// LINQ
var groupByCompoundKey = students.GroupBy(student => new{FirstLetterOfLastName = student.LastName[0],IsScoreOver85 = student.Scores[0] > 85}).OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);
嵌套
var nestedGroupsQuery =(from student in studentsgroup student by student.Year into newGroup1)from newGroup2 in(from student in newGroup1group student by student.LastName)group newGroup2 by newGroup1.Key;
// Linq
var nestedGroupsQuery =students.GroupBy(student => student.Year).Select(newGroup1 => new{newGroup1.Key,NestedGroup = newGroup1.GroupBy(student => student.LastName)});
异步
核心是Task
和Task<T>
对象与关键字async和await支持
- I/O绑定代码,等待一个在
async
方法中返回Task或Task的操作 - 如果会“等待”某些内容,则选择I/O绑定,eg.数据库数据,使用async和await
- 对于CPU绑定代码,等待使用Task.Run方法在后台线程启动的操作
- 需要执行开销大的计算且重视响应能力,则选择CPU绑定,在另一个线程上使用Task.Run生成工作。如适合并发和并行,还应考虑任务并行库