C#个人笔记 前言 异步任务 C#读取CSV,存入数据库 Split(',')过滤掉双引号内的逗号 用好代码代替注释 C#的13种基元类型 避免使用隐式类型 引用参数ref和输出参数out 泛型 委托 反射

记录一下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:360MoveDataUsersJustinDesktopdgkdataAudio 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的一行内容

用好代码代替注释

如开发人员发现需要写注释才能说清楚代码块的功用,应考虑重构,而不是洋洋洒洒写一堆注释。写注释来重复代码本来就讲得清的事情,只会变得臃肿,降低可读性,还容易过时,因为将来可能更改代码但没有来得及更新注释。

设计规范

  1. 不要使用注释,除非代码本身“一言难尽”。

  2. 要尽量写清楚的代码而不是通过注释澄清复杂的算法。

C#的13种基元类型

所谓的基元类型,就是C#中的所有类型的基础,分别有8种整数类型,2种小数类型,1种金融类型,1种布尔类型,1种字符类型:

C#个人笔记
前言
异步任务
C#读取CSV,存入数据库
Split(',')过滤掉双引号内的逗号
用好代码代替注释
C#的13种基元类型
避免使用隐式类型
引用参数ref和输出参数out
泛型
委托
反射

C#个人笔记
前言
异步任务
C#读取CSV,存入数据库
Split(',')过滤掉双引号内的逗号
用好代码代替注释
C#的13种基元类型
避免使用隐式类型
引用参数ref和输出参数out
泛型
委托
反射

金融类型是Decimal,布尔Bool,字符类型char

避免使用隐式类型

所谓的隐式类型就是var

var name = "许嵩";
string name = "许嵩";

我使用var或者string都是一样的,在最终的CIL代码里面也没区别,但是,如果确定类型,还是直接指定类型好,一目了然

引用参数ref和输出参数out

先说结论

  1. ref参数:将变量带入一个方法中改变之后在带出方法,ref参数使用前必须赋值

  2. 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);
        }

    }

泛型的使用方法就是

  1. 类后加

  2. 方法类型使用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();