数据类型
简单类型
值类型
布尔型
- bool类型数据只允许取值true或false,不可以0或非0的整数代替-true和false。
- 在C#语言中,bool类型不能像C++语言那样直接转换为int类型
字符型
- 在表示一个字符常数时,单引号内的有效字符数量必须有且只能是一个,而且不能是单引号或者反斜杠(\)。
转义符转义符
引用类型
- 根据类创建的对象都是引用类型
- 特殊的引用类型:object,string,数组
obeject类
- object是C#中所有类型(包括所有的值类型和引用类型)的基类。因此,对一个object的变量可以赋予任何类型的值。
- 装箱(Boxing)与拆箱 (unboxing):
字符串类型
- “@” 加在字符串前时,所有字符将不需要经过转义。
当字符串中包含反斜杠、双引号、换行符时可以使用。
特殊类型
推断类型
- 类型由编译器推断,在编译时确定。
Nullable类型
- 允许赋值为Null
Dynamic类型
- 编译时不检查,运行时才确定,主要用于与COM组件或其他语言交互
垃圾回收
CLR的GC(Garbage Collector)自动删除堆上不再访问的数据。
常量变量与命名
命名建议:
- 可以使用“重构”改名(点右键—重构—重命名)
- 驼峰命名法
运算符与表达式
- ^是异或操作符,不是乘方
C#没有乘方运算符。乘方使用Math.Pow函数
算术计算
- 除法/:运算结果绝对值为实际运算结果绝对值向下取整
- 取余 %:c=a%b,c符号与a相同,|c|=|a|-n*|b|
- 计算溢出问题:借助checked函数,捕获异常OverFlowException.
1.0/0.0是Infinity,0.0/0.0是NaN
- “+” 运算符两侧的操作数中只要有一个是字符串(String)
类型,系统会自动将另一个操作数转换为字 符串然后再进行连接
逻辑运算符
- && : 第一个操作数为假则不判断第二个操作数
|| : 第一个操作数为真则不判断第二个操作数
赋值运算符
- 值类型的变量赋值:复制值
引用类型的变量复制:复制引用
类型转换
值类型转换
- 强制转换: (类型标识符)表达式,如(int)1.23
- 隐式转换:源类型的数值范围是目标类型数值范围的子集,是安全的转换,不会造成数据越界。
引用类型转换
- 引用类型的赋值:复制引用
- 转型异常):强制转型虽然可以编译通过,但在运行时,如果实际对象的类型不是变量类型(或者子类)的时候,会抛出InvalidCastException
- 如何避免InvalidCastException:
字符串和数值的类型转换
- 采用以下方法进行转换,错误会抛出异常Eception, 可以输出其message查看
流程控制语句
分支语句–if
- 利用条件返回if-return,使嵌套结构变为顺序结构(在一个方法中,嵌套层次一般不要超过3层)
分支语句-- switch语句
- C#的Switch语句与C++/Java不同之处:
分支表达式除了整型、字符、枚举型,还可以用字符串
每个分支只有没有语句时,或者return/throw/goto等跳转语句时,才能省略后续的break
循环语句
for、while、do-while与c++语法相同
continue和break
数组
数组初始化
- 与Java语法相同,与C++语法不同,注意括号位置。
声明数组时不能指定其长度(数组中元素的个数)
- 动态初始化:数组定义与用new为数组元素分配空间并赋值的操作分开进行
- 静态初始化:在定义数组的同时就为数组元素分配空间并赋值
数组元素的默认值
- 数组是引用类型,它的元素相当于类的成员变量
- 数组一经分配堆空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。
- 数组是一个对象,属性Length指明它的长度
多维数组
交错数组
- 交错数组是数组的数组,因此可以是不规则的
- 交错数组的声明和初始化应按从高维到低维的顺序进行
数组基类Array
foreach语句
foreach是只读式的遍历,不能修改数组元素
- 若要实现一边遍历数组一边操作数据for循环倒序遍历或迭代器
for(int i = arr.length()-1;i>=0;i–)
Ref参数和Out参数
- 使用Ref参数,可以将变量的引用(相当于栈地址,不是堆地址)传递给被调方法
- ref修饰的变量在传递参数前要有初始值
- 一个方法返回多个值,可由out关键词标识的输出参数实现
- out修饰的参数要在方法中赋予初始值后,才能使用
- 离开方法前必须给out参数赋值
- out参数传递在前可以没有初始值(要在调用传参数括号中声明out参数类型)
- 甚至不需要预声明(不声明时要在调用传参数括号中声明out参数类型)
类
字段、方法
- 类(class)最基本的要素是
字段(field): 成员变量
方法(method):函数
- 使用构造方法调用另一个构造方法
- 由于C#自动进行对象的释放,所以用户一般不需要定义析构方法
- 构造方法不能显式地直接调用,而是用new来调用
属性
索引器
- 索引器可以看作是带参数的属性。
索引的参数可以为整数、字符串等任意数据类型
参数数目可以允许一到多个。
常来对内部的数组/集合等字段的元素进行读写。
- 索引器的定义与使用
- 属性与索引器比较
类的继承
- New关键词
新建子类成员,隐藏父类成员
是与父类完全无关的新成员(拒绝继承父类成员)
- Override关键词
重写父类的虚方法、虚属性、虚索引(父类预定义的扩展点)
可以供父类实现多态
- 使用override的方法,在调用时,会调用对象的实际类的方法。
使用new的方法,在调用时,会调用对象的申明类的方法。
- 使用base 访问父类的字段和方法
- 指向子类对象的父类引用不能调用子类特有的属性、方法
class A:{
}
class B:A{
}
A obj = new B();
修饰符
访问控制符
static
- static可以修饰字段、方法、属性
- static方法中,不能访问实例变量
const及readonly
sealed及abstract
sealed及abstract
接口
- 比抽象类更“抽象”
只允许抽象成员,不允许非抽象成员
- .Net中定义了大量接口
- 定义接口时不加 public abstract 这两个关键词
- 如果一个类实现多个接口的同名方法,为了消除歧义,需要显式写出接口名。
方法名前写接口名。调用时,只能用接口调用
结构与枚举
泛型
- 类似与C++中的模板
- ArrayList中的元素没有类型限制,可以自由存放任何类型的数据。无意间存入非法的数据,编译系统无法检测出错误,但运行时会造成系统出错。
- 如果使用泛型的List,编译器可以直接检查出类型错误
- 泛型—参数化类型
- 自定义泛型-泛型类
- 泛型方法
委托
- 相当于函数指针
委托作为方法的参数
- 调用方法时可以传入委托实例、Lambda式、函数名
C#内置泛型委托Action和Func
多播委托
- 一个委托实例中可以“包含”多个函数
- 调用委托,就是调用其中多个函数
- 多个函数间的先后顺序是没有意义的
- 返回值也就没有太多意义
C#中的事件
- 只能在申明事件的类里面触发该事件
Lambda 表达式
- Lambda 表达式是一种匿名函数
- Lambda 表达式和委托具有等价的效果
异常处理
- 同时捕获多个异常时,先子类异常再到父类异常,从特殊到一般
- 即便try、catch有return语句,也要先强制执行finally语句块再执行return语句
- 几种常用的异常类
- 创建用户自定义异常类
- 抛出异常
- 异常的传播
特性(Attribute)
- Attribute是与类、结构、方法等元素相关的额外信息, 是对元信息的扩展
- 自定义Attribute
基础类
Object类
- Object是所有类的基类
- 所以,任何对象都有以下方法(可以重写Object的这些方法)
Equals() :判断两个对象相等
ToString():将对象信息转换为一个字符串
GetType():获得对象的类型
对象的相等
- == 对于简单数据类型,表示其值相等;对于引用类型表示是同一个对象。如果一个类没有重写Equals,那么Equals和==意义相同。
- Equals的重写,需要同时重写GetHashCode方法
-重写GetHashCode方法时,非数字类型处理
EqualityComparer.Default.GetHashCode(GoodsName);
- 注意:String虽然是引用类型,但重载了== 运算符,所以==也表示数值相等
ToString 方法
- 自动调用:在把对象当作字符串输出或者连接时。
- 重写ToString:
对于一个引用类型,如果不重写ToString,那么会使用Object类的缺省ToString,把带命名空间的类名显示处理。重写以后,可以自己决定输出哪些字段信息。
浅克隆和深克隆
- 浅克隆:使用MemberwiseClone(),只复制一层
- 深克隆:一层层复制引用字段的各层元素
- 浅克隆p1、p3指向同一个Birthday对象
Math 类
Random 类
DateTime 及TimeSpan
String 类
String 及 StringBuilder
集合类
- ArrayList和List< T >
- 栈:Stack< T>
- 队列:Queue< T>
- 链接表:LinkedList< T>
- 字典:Dictionary
添加迭代器(Enumerator)
- yield return 语句可一次返回一个元素
集合元素的排序
- 使用有序集合
- 直接使用Sort(),会抛异常InvalidOperationExpression
- 待排序类实现IComparable接口,然后就可以使用Sort()排序
- 比较器类实现IComparer接口
- 使用Lambda表达式
p1表示当前对象,p2表示待比较的对象。
LINQ语言
- LINQ的两种语法形式
- LINQ的数据类型IEnumerable< T>,T取决于select子句的结果。一般使用推断类型var表示,编译器会自己判断类型
From…Where
- From部分可以省略为数组或集合对象
- 可以不写select子句,表示返回的就是数组/集合的元素
- Where部分使用lambda表达式来写
- 多个&&条件也可以写成多个Where语句
- 二次过滤
可以从上次查询的结果中进行查询。
原理:in 后面跟的数据类型是IEnumerable接口
Select子句
- 指定返回数据
- 可以直接返回整个元素,也可以返回一部分属性。
- 匿名数据类型
OrderBy子句
多字段排序
- 多字段排序
LINQ查询结果
- 结果数据转换
- 结果数据计算
流与二进制输入/输出
Stream类
FileStream:对文件进行读写
FileMode:包括Create,CreateNew,Open,OpenOrCreate(此模式不适用于多次写入)
FileAccess: 包括Read,Write,ReadWrite
WriteByte:写一个字节
Write:写一个字节数组,从下标为0开始,一共写3个字节
Close:关闭文件流,会将没有写完的数据写完,然后释放资源
ReadByte:读一个字节。如果已经结束,返回-1
MemoryStream:对一个byte数组进行流式读写
new MemoryStream(ary):创建内容为ary,长度固定的内存流。
new MemoryStream(): 创建长度可自动增加的内存流
ToArray(): 写入到一个新数组中
BufferedStream
缓存操作,用于减少对文件读写次数 ,一般不单独使用,会包装到其他类型的流中
读写器类
通过流读写基本数据类型(int、double等),而不是原始的字节类型,使用起来更方便
二进制读写
- BinaryReader和BinaryWriter
文本读写
- StreamReader
建立StreamReader对象:new StreamReader(文件路径,FileEncode)
主要方法:ReadLine 、ReadToEnd 、Read 、Peek 、Close
- StreamWriter
建立StreamWriter对象:new StreamWriter(文件路径,FileMode,FileEncode)
主要方法:WriteLine 、Write 、Flush 、Close
- 使用File类的静态方法进行文本读写
非托管资源的释放
- 非托管资源
不能被.Net的垃圾回收及时释放的资源
包括文件,窗口,网络连接,数据库连接,画刷,图标等
非托管资源的类中都实现了IDisposable接口
需要明确使用close(调用Dispose)进行关闭
- 释放方式1: 在finally中关闭资源,使用close(调用Dispose)
- 释放方式2: 使用Using语句来管理资源
序列化与反序列化
- 序列化:对象 -> Byte数组/文本
- 反序列化:Byte数组/文本 -> 对象
- 可序列化类加[Serializable]标注
- 公有属性才能可序列化
- 使用BinaryFormtter的Serialize和Deserialize方法
- 使用XmlSerializer的Serialize和Deserialize方法
文件、目录
文件及文件夹相关的类
- 实体类
名字带“Info”的类,如FileSystemInfo 、FileInfo、DirectoryInfo、DirveInfo
代表一个文件、文件夹、驱动型对象。
FileSystemInfo(抽象类):是FileInfo、DirectoryInfo的父类
- 工具类
如:File、Directory、Path
提供静态方法,对文件、文件夹和路径进行操作。
FileInfo 对象及属性
建立对象
■ new FileInfo(文件物理路径) //注:不是创建文件!
常用属性
■ Name 文件名称
■ Extension 文件扩展名
■ FullName 文件完全路径(物理路径)
■ Length 文件大小,单位为字节
■ CreationTime 文件创建时间
■ LastAccessTime 文件上次访问时间
■ LastWriteTime 文件上次修改时间
■ DirectoryName 所在文件夹
■ Attributes 文件属性,如只读、隐藏等
DirectoryInfo 对象及其属性
建立该对象
■ new DirectoryInfo(文件夹物理路径)
常用的属性:
■ Name 文件夹名称
■ FullName 文件夹完全路径(物理路径)
■ CreationTime 文件夹创建时间
■ LastAccessTime 文件夹上次访问时间
■ LastWriteTime 文件夹上次修改时间
■ Parent 父文件夹
■ Root 所在根目录
文件的新建、复制、移动和删除
使用File工具类
■ Create(filePath)
■ Copy(filePath1,filePath2)
■ Move(filePath1,filePath2)
■ Delete(filePath)
■ Exists(filePath)
■ CreateText(filePath):创建一个文本文件,返回StremWriter对象
■ OpenText(filePath):打开一个文本文件,返回StremReader对象
文件夹的新建、移动和删除
使用Directory工具类
■ CreateDirectory (DirPath)
■ Move(DirPath1,DirPath2)
■ Delete(DirPath)
■ Exists(DirPath)
■ GetDirectories(DirPath)
■ GetFiles(DirPath)
显示指定文件夹下的子文件夹和子文件
使用Directory工具类
■ GetDirectories和GetFiles的static方法。
使用DirectoryInfo对象
■ GetDirectories和GetFiles实例方法
■ 这两个方法分别返回DirectoryInfo对象数组和FileInfo对象 数组。
■ GetFileSystemInfos
■ 返回FileSystemInfo对象数组,包括所有子文件和子文件夹。
- 示例:显示文件夹内的所有文件
Path 工具类: 提供路径字符串的处理
- GetDirectoryName
- GetExtension
- GetFileName
- GetFileNameWithoutExtension
- GetFullPath
- GetPathRoot
- GetTempFileName
- GetTempPath
- Combine
Enviroment工具类
文件系统变更监听
- 可以用FileSystemWatcher 来监视某一个文件和文件夹的变化情况
- FileSystemWatcher 可以监视某一个文件和文件夹的变化情况
单元测试
- 测试框架
VS内置:MSTest、MSTest V2
第三方:NUnit 和 xUnit
- 方法1: 类名上右键“创建单元测试”,自动生成测试项目和测试用例的框架。
- 方法2:手工创建建立单元测试项目
- 测试断言:
Assert.AreEqual 验证指定的两个对象是否相等
Assert.AreNotEqual验证指定的两个对象是否不相等
Assert.AreNotSame验证两个对象变量是否引用不同对象
Assert.AreSame验证两个对象变量是否引用相同对象
Assert.IsFalse验证指定的条件是否为false
Assert.IsTrue 验证指定的条件是否为true
Assert.IsNotNull 验证指定的对象是否不为Null
Assert.IsNull 验证指定的对象是否为Null
数据绑定
- 简单数据绑定
- textBox1.DataBindings.Add(“Text”,order, “Status”);
将对象order的Status属性与控件textBox1的Text属性绑定
- DataBindings绑定的是对象,而不是变量
也就是说如果被绑定的变量赋了其他的对象,控件还是和原对象绑定
- 集合数据绑定
DataGridView 数据表格控件
BindingNavigator 数据导航控件
DataSet 代表数据库等数据在内存中的缓存,由多个DataTable组成
BindingSource 数据绑定控件,可以将对象、数组、List、DataTable、BindingList等数据与控件绑定。控件绑定到BindingSource,BindingSource再绑定到集合数据
- 主从数据绑定
OrderBindingSource绑定类型选择Order类(注意在代码中还要给BindingSource的DataSource绑定具体的Order类实例或列表)。 ItemBindingSource绑定类型选择OrderBindingSource,然后DataMember选择Order类的ItemList属性(注意只要public属性才能被设置成DataMember),从而实现主从数据绑定。将BingdingSource实例绑定到DataGridView等显示容器的DataSource属性上可实现数据展示。
OrderBindingSource.DataSource = Server.OrderList;
private void Form2_FormClosed(object sender, FormClosedEventArgs e)
{
this.DialogResult = DialogResult.OK;
}
if(form2.ShowDialog()==DialogResult.OK)
OrderBindingSource.ResetBindings(false);
Order order = orderBindingSource.Current as Order;
- saveFileDialog和openFileDialog
Filter属性指定文件类型
FileName获取文件名
private void btnExport_Click(object sender, EventArgs e) {
DialogResult result = saveFileDialog1.ShowDialog();
if (result.Equals(DialogResult.OK)) {
String fileName = saveFileDialog1.FileName;
orderService.Export(fileName);
}
}
网络编程
Socket编程
TCP通信
UDP通信
Web信息获取
Get和Post
- GET方法
没有HTTP消息体
请求参数一般附加在URL上
- POST方法
有HTTP的消息体
请求数据一般放在消息体内,也可以放在URL上
System.Web
System.Web提供支持浏览器/服务器通讯的类和接口。
此命名空间包括提供有关当前 HTTP 请求的大量信息的 Request 类、管理 HTTP 到客户端的输出的 Response类,以及提供对服务器端实用工具和进程的访问的HttpServerUtility 对象。
System.Web 还包括用于 Cookie 操作、文件传输、异常信息和输出缓存控制的类。
System.Net中的类
WebClient类
E-Mail编程
- 使用MailMessage类和SmtpClient发送Email
private void btnSend_Click(object sender, EventArgs e) {
MailMessage message = new MailMessage();
SmtpClient SmtpServer = new SmtpClient(txt_smtpserver.Text);
message.From = new MailAddress(txtFrom.Text);
message.To.Add(txtTo.Text);
message.Subject = txtSubject.Text;
message.Body = rtxMessage.Text;
SmtpServer.Port = 25;
SmtpServer.Credentials =new NetworkCredential(
txtFrom.Text, txtPassword.Text);
SmtpServer.Send(message);
}
正则表达式
Regex类
- 命名空间:System.Text.RegularExpressions;
- 静态方法
Regex.IsMatch( s, pattern ) //返回布尔值,相当于string的contain方法
- 非静态方法
创建对象regex=new Regex(pattern);
regex.IsMatch(s) //返回布尔值
regex.Match(s) //返回第一个匹配(Match对象)
regex.Matches(s) //返回所有匹配
regex.Replace(s, r) //将匹配子串替换为另一个字符串
正则表达式
string pattern = @"(\w+)\s(\1)";
string input = "He said that that was the the correct answer.";
MatchCollection matches = Regex.Matches(input, pattern, RegexOptions.IgnoreCase);
foreach (Match match in matches)
Console.WriteLine("Duplicate '{0}' found at positions {1} and {2}.",
match.Groups[1].Value,
match.Groups[1].Index,
match.Groups[2].Index);
pattern = @"(?\w+)\s\k\W(?\w+)";
input = "He said that that was the the correct answer.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("A duplicate '{0}' at position {1} is followed by '{2}'.",
match.Groups["word"].Value, match.Groups["word"].Index,
match.Groups["nextWord"].Value);
XML
XML文档的基本结构
XML的基本处理方式
DOM(Document Object Model)
读取全部文档,形成一个树,对树进行操作。
相关类:XmlDocument
SAX(Simple API for XML)
逐行读取文档,依次处理各个元素
相关类:XmlTextReader、XmlTextWriter
XPath的概念
XmlElement root = doc.DocumentElement;
XmlNodeList nodes = root.SelectNodes(“/book/title”);
XmlNode node = root.SelectSingleNode( “/book[1]/@isbn”);
使用XSLT进行转换
线程及其控制
基本使用
Thread thread = new Thread( obj.fun);
thread.Start();
Thread thread3 = new Thread(()=>obj2.Count2(15));
- 线程的启动和停止
启动:调用线程对象的Start()
正常终止:等到线程函数执行完毕,线程结束
强制终止:Abort()
挂起和恢复:Suspend() 挂起 Resume() 恢复
暂停一段时间:Sleep(毫秒数)
System.Threading.Thread属性
前台线程与后台线程
- 前台线程
主线程是前台线程
可以有多个前台线程,只有所有前台线程都执行完后,进程才会中止。
- 后台线程
不阻止进程结束
当前台进程终止时,自动中断后台线程
线程的优先级 ThreadPriority
线程的状态 ThreadState
System.Threading.Thread方法
- Join-线程的同步
t1.Join() : 等待t1线程执行完毕后,再执行后面的语句
数据争用的解决:使用Lock语句为对象上锁
- lock(objectA) { codeB} 的作用
objectA被锁了吗?没有则由我来加锁。否则一直等待,直至对象被释放。
lock以后在执行codeB的期间,其他线程不能调用codeB,也不能使用objectA。
执行完codeB之后释放objectA,并且codeB可以被其他线程访问。
线程池
- 通过排队机制,限制最多同时运行的线程数目,减少资源占用。
将池中已经释放的线程,分配给新任务,减少线程创建时间。
- 工作线程: 进行CPU计算的线程
异步I/O线程:等待I/O结果的线程
设置的线程池的大小要大于等于CPU核数
Timer类
- 定时器
定时触发,调用某个方法
- 创建定时器
Timer timer = new Timer(被调方法,状态对象, 开始时间, 触发间隔);
- 停止定时器
timer.Dispose();
集合的线程安全性
- 线程安全
某个类的对象被多线程访问和单线程访问,都不会出现异常结果。
线程安全的对象在多线程访问时不会出现“数据争用”问题。
- 集合的线程安全
集合对象常常被多线程访问和修改
多个线程同时添加元素,可能会相互覆盖,造成元素数目不对。
- 非泛型集合类的线程安全
Array,ArrayList,SortedList,Hashtable等,都可以使用Synchronized()方法获取一个线程安全的包装对象
- 泛型集合类的线程安全
在System.Collections.Concurrent中提供了一组线程安全的集合类
- 线程安全的集合类
BlockingCollection:实现生产/消费模式的线程安全的集合,提供阻塞和限制功能
ConcurrentBag:表示对象的线程安全的无序集合
ConcurrentDictionary:表示可由多个线程同时访问的键值对的线程安全集合
ConcurrentQueue:表示线程安全的先进先出 (FIFO) 集合。
ConcurrentStack:表示线程安全的后进先出 (LIFO) 集合。
并行编程
- 并发(同时发生)
CPU分时执行多个任务,单核也可并发
目的:提升用户体验
- 并行(同时执行)
将任务分解为多个子任务在多CPU/多核上执行
目的:提高执行效率
并行任务库
Task类的使用
- 启动Task
使用Task.Run方法来得到Task的实例
Task task = Task.Run( ()=>SomeFun() );
- 等待任务结束
Task.WaitAll( task数组)
- 取任务的结果
double result =task.Result; //等待直到获得结果
- Task同步: 让一个任务结束自动启动另一个任务
Task task2 = task1.ContinueWith(
(PrevTask) => {DoStep2();});
task1.Start();
- Task中的异常:使用AggregateException(合并的异常)
try{
Task.WaitAll(task1, task2, task3);
}catch (AggregateException ex){
foreach (Exception inner in ex.InnerExceptions){
Console.WriteLine(
"Exception type {0} from {1}",
inner.GetType(), inner.Source);
}
}
Parallel类的使用
- 并行执行多个任务,直到完成
- 并行For和ForEach方法无序
- 示例:Parallel.Invoke
并行Linq(PLinq)
- 在集合上加个 .AsParallel()
- 查询结果使用ForAll进行遍历
var query= from n in persons.AsParallel()
where n.Age > 20 && n.Age < 25 select n;
query.ForAll(i=>Console.WriteLine(i))
异步编程
- 开始一个任务后,让任务在另一个线程中执行,本线程继续执行后续语句
- async修饰一个方法,方法中需要有await语句
- await表示异步方法开始等待,主线程继续执行
异步网络下载
异步文件流
- 与上面的HttpClient相似,StreamWriter等类也提供了异步方法
async static void WriteFile() {
using (StreamWriter sw = new StreamWriter(
new FileStream("aaa.txt", FileMode.Create))) {
await sw.WriteAsync("My Text");
}
Console.WriteLine("Write File OK!");
}
多线程更新Winform
- WinFrom控件的操作:只能在创建控件的线程里面进行设置属性和调用方法
- 解决方法
控件Invoke:同步调用
控件的beginInvoke方法:异步调用
WebApi
- 测试api时需要在Postman设置关闭ssl证书验证
- 测试api时需要在Client通过以下代码关闭ssl证书验证
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = delegate { return true; };
var client = new HttpClient(handler);