C#个人笔记
前言
记录一下C#的一些东西,基础好多还是不会,还是推荐微软的官网文档,网上的博客写的都太水了,还是官网文档好一点
异步任务
同步方法的缺点
其实我最想讲的就是这个,我举个例子,有两个方法,方法1和方法2,我现在想先执行方法1再执行方法2,如果我顺序执行的话,那么必须等待方法1执行完成之后才执行方法2 代码如下
static void Main(string[] args)
{
method1();
method2();
}
public static void method1()
{
for (int i = 0; i < 80; i++)
{
System.Console.WriteLine("method1: "+i);
}
}
public static void method2() {
for (int i = 0; i < 20; i++)
{
System.Console.WriteLine("method2: "+i);
}
}
执行一下就知道了,必须等待方法1执行完才会执行方法2,就比如我想烧水做饭,必须先等水烧开了我才能洗菜切菜......这明明是可以同时做的事情,我们可以使用异步方法解决
异步方法
这个分为两种情况,I/O和CPU运算,我这里暂时没用到I/O所以不写了,讲讲CPU运算的
返回Task
static void Main(string[] args)
{
method1();
method2();
System.Console.ReadKey();
}
public static async Task method1()
{
await Task.Run(() =>
{
for (int i = 0; i < 80; i++)
{
System.Console.WriteLine("method1: " + i);
}
});
}
public static void method2() {
for (int i = 0; i < 20; i++)
{
System.Console.WriteLine("method2: "+i);
}
}
特点就是async,Task或者Task<T>
,await,Task.Run这几个
返回Task<T>
static void Main(string[] args)
{
callMethod();
System.Console.ReadKey();
}
public static async void callMethod()
{
Task<int> task = method1();
int count = await task;
method3(count);
}
public static async Task<int> method1()
{
int count=0;
await Task.Run(() =>
{
for (int i = 0; i < 80; i++)
{
System.Console.WriteLine("method1: " + i);
count++;
}
});
return count;
}
public static void method2()
{
for (int i = 0; i < 20; i++)
{
System.Console.WriteLine("method2: " + i);
}
}
public static void method3(int count)
{
System.Console.WriteLine("Count is "+count);
}
C#读取CSV,存入数据库
C#读取CSV的内容,以DataTable的格式返回
string path = @"D:\360MoveData\Users\Justin\Desktop\dgkdata\Audio Products~Accessories.csv";
public static DataTable ReadData(string filePath)
{
//Encoding encoding = Common.GetType(filePath); //Encoding.ASCII;//
Encoding encoding = Encoding.ASCII; //Encoding.ASCII;//
DataTable dt = new DataTable();
FileStream fs = new FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
//StreamReader sr = new StreamReader(fs, Encoding.UTF8);
StreamReader sr = new StreamReader(fs, encoding);
//string fileContent = sr.ReadToEnd();
//encoding = sr.CurrentEncoding;
//记录每次读取的一行记录
string strLine = "";
//记录每行记录中的各字段内容
string[] aryLine = null;
string[] tableHead = null;
//标示列数
int columnCount = 0;
//标示是否是读取的第一行
bool IsFirst = true;
//逐行读取CSV中的数据
while ((strLine = sr.ReadLine()) != null)
{
if (IsFirst == true)
{
tableHead = strLine.Split(',');
IsFirst = false;
columnCount = tableHead.Length;
//创建列
for (int i = 0; i < columnCount; i++)
{
DataColumn dc = new DataColumn(tableHead[i]);
dt.Columns.Add(dc);
}
}
else
{
//MySplit这个方法看下面的介绍
List<string> dataList = MySplit(strLine);
aryLine = dataList.ToArray();
DataRow dr = dt.NewRow();
for (int j = 0; j < columnCount; j++)
{
dr[j] = aryLine[j];
}
dt.Rows.Add(dr);
}
}
if (aryLine != null && aryLine.Length > 0)
{
dt.DefaultView.Sort = tableHead[0] + " " + "asc";
}
sr.Close();
fs.Close();
return dt;
}
然后接受这个DataTable
//先获取所有的列名
DataTable dt = Read.ReadData(path);
string[] strColumns = null;
if (dt.Columns.Count > 0)
{
int columnNum = 0;
columnNum = dt.Columns.Count;
strColumns = new string[columnNum];
for (int i = 0; i < dt.Columns.Count; i++)
{
strColumns[i] = dt.Columns[i].ColumnName;
}
}
//在遍历开始处理数据
foreach (DataRow dataRow in dt.Rows)
{
foreach (var columsName in strColumns)
{
switch (columsName)
{
case "Datasheets":
break;
case "Image":
break;
处理逻辑......
}
string aaa = dataRow[columsName].ToString();
处理逻辑......
Console.WriteLine(aaa);
}
}
Split(',')过滤掉双引号内的逗号
这个也可以叫做,C#读取CSV文件逗号问题
我读取的一串字符串是这样的
"许嵩","蜀,云泉",1,22,"音乐"
我使用Split(',')之后蜀云泉就分开了,这显然不是我要的结果
解决方法可以使用正则,但是我不会写,所以写一个最基础的substring
private static List<string> MySplit(string str)
{
const char mark = '"';
const char comma = ',';
bool startMark = false;
int startIndex = -1;
int endIndex = -1;
List<string> myList = new List<string>();
for (int i = 0; i < str.Length; i++)
{
if (str[0] == comma)
{
myList.Add("");
}
if (startMark && str[i] == comma)
{
continue;
}
if (str[i] == comma && i > 0)
{
endIndex = i;
}
if (str[i] == mark && !startMark)
{
startMark = true;
}
else if (str[i] == mark && startMark)
{
startMark = false;
}
if (startIndex == -1)
{ startIndex = i; }
if ((startIndex >= 0 && endIndex > 0) || (endIndex == -1 && i == str.Length - 1))
{
if (endIndex == -1)
{
endIndex = i + 1;
}
myList.Add(str.Substring(startIndex, endIndex - startIndex));
startIndex = -1;
endIndex = -1;
}
}
return myList;
}
这个strLine就是C#读取CSV的一行内容
用好代码代替注释
如开发人员发现需要写注释才能说清楚代码块的功用,应考虑重构,而不是洋洋洒洒写一堆注释。写注释来重复代码本来就讲得清的事情,只会变得臃肿,降低可读性,还容易过时,因为将来可能更改代码但没有来得及更新注释。
设计规范
-
不要使用注释,除非代码本身“一言难尽”。
-
要尽量写清楚的代码而不是通过注释澄清复杂的算法。
C#的13种基元类型
所谓的基元类型,就是C#中的所有类型的基础,分别有8种整数类型,2种小数类型,1种金融类型,1种布尔类型,1种字符类型:
金融类型是Decimal,布尔Bool,字符类型char
避免使用隐式类型
所谓的隐式类型就是var
var name = "许嵩";
string name = "许嵩";
我使用var或者string都是一样的,在最终的CIL代码里面也没区别,但是,如果确定类型,还是直接指定类型好,一目了然
引用参数ref和输出参数out
先说结论
-
ref参数:将变量带入一个方法中改变之后在带出方法,ref参数使用前必须赋值
-
out参数: 在返回多个值的时候使用out参数,使用前不需要赋值
举个例子,代码如下
static void Main(string[] args)
{
int salary = 5000;
jiangJin(salary);
Console.WriteLine(salary);
Console.Read();
}
static void jiangJin(int salary)
{
salary += 500;
}
像这个例子,输出的salary还是5000,虽然我经过了jiangJin方法的计算,但是我没有return计算后的结果,所以不管方法内怎么计算了,只要不return,salary值没变
现在我加一个ref就不同了
static void Main(string[] args)
{
int salary = 5000;
jiangJin(ref salary);
Console.WriteLine(salary);
Console.Read();
}
static void jiangJin(ref int salary)
{
salary += 500;
}
我就加了一个ref,然后salary的输出结果就是5500了,不需要return了
所以ref参数的作用是:将变量带入一个方法中改变之后在带出方法,以传引用的方式来传变量,而不是值拷贝的方式
out输出参数其实和ref功能一模一样,但是out输出参数更注重检查方法内是否对out参数进行赋值,代码如下
static void Main(string[] args)
{
int a = 1;
int b;
int asd = Calcu(a,out b);
Console.WriteLine(asd + " : " + b);
Console.Read();
}
static int Calcu(int a, out int b)
{
b = a;
return a + b;
}
泛型
复制代码的麻烦
我现在写一个类,如下
public class StudyT
{
public void Add(string name)
{
Console.WriteLine("我是增加方法,变量是:" + name);
}
}
然后我可以实例化调用
StudyT<string> studyT = new StudyT<string>();
studyT.Add("许嵩");
但是我的Add方法,我希望string类型可以,int类型可以,float类型的也可以使用,那我怎么办呢?
复制一下StudyT类,然后Add方法的参数类型改为int,这当然ok,但是麻烦
Object的装箱拆箱损失性能
所以我选择使用Object类型,如下
public class StudyT
{
public void Add(object name)
{
Console.WriteLine("我是增加方法,变量是:" + name);
}
}
非常好,Object是基类,这下我传入int,string,float都可以用,但是又来了一个新问题,Object转化的时候有装箱拆箱,损失性能了,而且还有赋值不检查类型的错误可能,所以,我选择使用泛型
泛型的好处
public class StudyT<T>
{
public void Add(T name)
{
Console.WriteLine("我是增加方法,变量是:" + name);
}
}
泛型的使用方法就是
-
类后加
-
方法类型使用T表示
这下我实例化对象调用的时候,传入什么类型,就是什么类型,而且还有类型检查,很安全
泛型的约束
我这个方法啊,只希望某个类或者某个接口才能使用,你给我传入一个int,string类型的没用,所以我做个约束,你传入的类型,必须是我想要的指定类型
public class StudyT<T> where T : IMovie
{
public void Add(T name)
{
Console.WriteLine("我是增加方法,变量是:" + name);
}
}
也很简单,直接 where T : Movie 即可,表明,传入的类型必须是继承了IMovie接口的,不管是大电影,微电影,动画片,科幻片啥的,只要继承了IMovie接口就能使用
委托
我终于知道委托和事件是干嘛的了,多亏我同学写的demo,不然我还是不理解委托
书上说委托可以解决大量if else的情况,百科也是这样说的,但是我没啥感觉,出了一个排序的例子,我没感觉其他例子可以解决大量if else的,暂时不管这个了
对于委托最好的理解和使用就是发布订阅模式了,在设计模式里面也称之为观察者模式
发布订阅模式
这个例子很清楚的讲解了委托的使用,我有3个类,服务器,客户端,消息管理类,代码如下
class Server
{
public void PublishInfo(string info)
{
Console.WriteLine($"服务器发布了新消息: {info}");
InformationManager.instance.Info = info;
InformationManager.instance.UpdateInformation?.Invoke();
}
}
class Client
{
string clientName = "";
public Client(string name,bool isSub = false)
{
clientName = name;
if (isSub)
{
InformationManager.instance.UpdateInformation += ReceiveInfo;
}
}
public void ReceiveInfo()
{
Console.WriteLine($"{clientName}用户收到了消息: {InformationManager.instance.Info}");
}
}
class InformationManager
{
private string mInfo;
public Action UpdateInformation;
//单例的消息管理器实例
private static InformationManager _instance;
public static InformationManager instance
{
get
{
if (_instance == null)
{
_instance = new InformationManager();
}
return _instance;
}
}
}
然后Main方法调用如下
Server server = new Server();
Client client = new Client("刘备",true);
Client client1 = new Client("关羽");
Client client2 = new Client("张飞");
server.PublishInfo("好消息,许嵩发新歌啦");
结果很Nice,这就是委托了
不安全的委托
我们在给委托添加方法的时候,使用的是+=
InformationManager.instance.UpdateInformation += ReceiveInfo;
但是有时候我们会不小心写成=,这样订阅者就会被覆盖,我有3个订阅者,结果写成了=,只有第3个订阅者收到消息了,前两个被覆盖了,这样很不好.
不要说你会小心的,你不会忘记写+=,这是无法避免的事情,因为我刚学的时候也总是忘记写成=号,这就是不安全的委托,所以我们需要修改一下,使用事件解决这个问题
事件,就是安全的委托
事件:安全的委托
上面说了,委托方法的+=很容易被写成=,这样不安全,所以我们改一下代码,使用事件,事件是安全的委托,因为事件强制你写+=
class Server
{
public void PublishInfo(string info)
{
Console.WriteLine($"服务器发布了新消息: {info}");
InformationManager.instance.Info = info;
//InformationManager.instance.UpdateInformation?.Invoke(); 如果是委托需要调用
}
}
class Client
{
string clientName = "";
public Client(string name,bool isSub = false)
{
clientName = name;
if (isSub)
{
//这里的委托必须是+=,写成=就覆盖了,虽然我知道,但是我又忘了,所以写成事件,事件强制+=,所以事件是安全的委托
InformationManager.instance.UpdateInformation += ReceiveInfo;
}
}
public void ReceiveInfo()
{
Console.WriteLine($"{clientName}用户收到了消息: {InformationManager.instance.Info}");
}
}
class InformationManager
{
private string mInfo;
//public Action UpdateInformation; 委托
public event Action UpdateInformation;
//单例的消息管理器实例
private static InformationManager _instance;
public static InformationManager instance
{
get
{
if (_instance == null)
{
_instance = new InformationManager();
}
return _instance;
}
}
/// <summary>
/// 这个方法是事件的时候才用的,委托是直接调用,事件是触发,所以触发
/// </summary>
public string Info
{
get => mInfo;
set
{
if (value != mInfo)
{
mInfo = value;
if (UpdateInformation != null)
{
UpdateInformation();
}
}
}
}
}
委托需要调用,而事件是用来触发的,所以在InformationManager加了一个触发事件
这次再给委托添加方法的时候试试,必须写成+=,这样就再也不怕写成=号了
反射
//反射第一种:GetType() 有实例对象,可以调用获取属性,方法,字段
DateTime dateTime = new DateTime();
Type type = dateTime.GetType();
PropertyInfo[] propertyInfos = type.GetProperties(); //所有的属性
MethodInfo[] methodInfos = type.GetMethods(); //所有的方法
FieldInfo[] fieldInfos = type.GetFields(); //所有的字段
//反射第二种:typeof() 没有实例对象的情况,比如静态类或单纯的类名
Type type1 = typeof(X);
PropertyInfo[] xpropertyInfos = type1.GetProperties(); //所有的属性
MethodInfo[] xmethodInfos = type1.GetMethods(); //所有的方法
FieldInfo[] xfieldInfos = type1.GetFields(); //所有的字段
Type type2 = typeof(StudyThread);
MethodInfo[] tmethodInfos = type2.GetMethods(); //所有的方法
StudyThread studyThread = (StudyThread)Activator.CreateInstance(type2);//Activator是根据Type获取实例对象
studyThread.Test();