C#基础进阶

十一、LINQ数据访问技术

LINQ查询表达关键字

关键字功能
from指定要查找的数据源及范围变量,多个from字句则表示从多个数据源查找数据
select指定查询要返回的目标数据,可以指定任何类型,甚至是匿名类型
where指定元素的筛选条件,多个where子句则表示并列条件,必须全部都满足才能入选
orderby指定元素的排序字段和排序方式,当有多个排序字段时,由字段顺序确定主次关系,可以指定升序和降序两种排序方式
group指定元素的分组字段
join指定多个数据源的关联方式

11.1 基础知识

11.1.1 IEnumerable可枚举类型

IEnumerable可枚举类型:只要一个类型实现了IEnumerable接口,就可以对其进行遍历(迭代),常用来处理内存中的数据

本质:直接在内存中查数据,对内存要求很高

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IEnumerable_Study
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //IEnumerable可枚举类型 -- 可迭代类型
            //IEnumerator枚举器
            //Enum 枚举

            //int[] array = new int[] {1,2,3,4,5};
            //foreach (int item in array)
            //{
            //    Console.Write(item+" ");
            //}
            //Console.ReadLine();

            //string str = "xiaoshu";
            //foreach (var item in str)
            //{
            //    Console.Write(item+" ");
            //}
            //Console.ReadLine();

            //思考?为什么不能遍历对象
            Student student = new Student();
            foreach (var item in student)
            {
                Console.WriteLine(item);
            }
            Console.ReadLine();
        }
    }
    //只要一个类型实现了IEnumerable接口,就可以对其进行遍历(迭代)
    //yield关键字:做了什么工作?
    //答:它是一个迭代器,它相当于实现了IEnumerator枚举器
    class Student : IEnumerable
    {
        public string Name { get; set; }

        public IEnumerator GetEnumerator()
        {
            //throw new NotImplementedException();
            //yield return "xiaoshu";
            string[] student = {"xiaoshu", "小舒"};
            return new StudentEnumerator(student);
        }
    }

    internal class StudentEnumerator : IEnumerator
    {
        public string[] _student { get; }
        public int _position = -1;
        public StudentEnumerator(string[] student)
        {
            _student = student;
        }

        public object Current 
        { 
            get 
            {
                if (_position == -1)
                {
                    throw new InvalidOperationException();
                }
                if (_position >= _student.Length)
                {
                    throw new InvalidOperationException();
                }
                return _student[_position];
            }
        }
        //就是让我们把操作推进到下一个
        public bool MoveNext()
        {
            if (_position<_student.Length-1)
            {
                _position++;
                return true;
            }
            else
            {
                return false;
            }
        }
        //恢复默认值
        public void Reset()
        {
            _position = -1;
        }
    }
}

11.1.2 扩展方法
// See https://aka.ms/new-console-template for more information

namespace ExtendMethod
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //扩展方法:静态类中的静态方法,参数列表最前面加个this + 要扩展的类型
            //使用场景:在不修改原代码的情况下,为其他类型增加方法或者功能
            //Calculate calculate = new Calculate();
            //var sum = calculate.AddThree(1,2,3);
            //Console.WriteLine(sum);

            //int? i = 0;
            //i.ToInt1();

            int? i = null;
            i.ToInt2();

        }
    }
    class Calculate
    {
        public int AddTwo(int a,int b)
        {
            return a + b;
        }
        //需求增加:需要三个数相加
        //扩展方法引入:不添加下面的方法代码,而实现需求
        //public int Add(int a, int b,int c)
        //{
        //    return a + b + c;
        //}
    }

    //扩展方法:静态类中的静态方法,参数列表最前面加个this + 要扩展的类型
    static class CalculateNew
    {
        public static int AddThree(this Calculate calculate, int a, int b, int c)
        {
            return a + b + c;
        }
        public static int ToInt1(this int? i)
        {
            if (i==null)
            {
                return 0;
            }
            else
            {
                return i.Value;
            }
        }
        public static void ToInt2(this int? i)
        {
            Console.WriteLine("这是ToInt2方法");
        }
    }
}
11.1.3 LINQ
  • LINQ to Object:数组、list集合(实现了IEnumerable接口的都可以查询)-- 内存里面的数据
  • LINQ to SQL:查询数据用的 – 在数据库里面的数据
  • LINQ to XML:查询XML文件
11.1.4 IQueryable类型及EF框架的数据库连接:基于SQL Server

IQueryable类型:IQueryable实现了IEnumerable接口,常用来处理数据库里面的数据
本质:将数据在数据库里查好之后,再返回到内存中

//AsQueryable():将masterList类型转换为IQueryable类型
var query = masterList.AsQueryable().Where(x => x.Id == 1);

EF框架的数据库连接:基于SQL Server

EF:Db First 、Model First 、Code First(代码优先:先把代码写出来,用代码去映射生成数据库)

以下操作基于Db First(数据库优先),先把数据库弄好,再做一个同步的映射对象

  1. 创建实体数据模型

image-20220928153521773

  1. 来自数据库的EF设计器

    image-20220928153653342

  2. 选择数据库连接

    image-20220928153729799

  3. 编写查询

    默认是IQueryable类型:只返回查询到的数据到内存

    转换成IEnumable类型后:将数据库中全部的数据都返回到内存,再做查询

    //DBEntities:是连接的数据库名字
    DBEntities db = new DBEntities();
    //db:代表数据库   Books:代表数据库的表名  BookId:代表数据库的字段名  注:默认是IQueryable类型
    var query = db.Books.Where(s => s.BookId>0)
                        .Where(s => s.BookName == "C#").ToList();
    
    DBEntities db = new DBEntities();
    //db:代表数据库   Books:代表数据库的表名  BookId:代表数据库的字段名  AsIEnumable():转换成IEnumable类型
    var query = db.Books.AsIEnumable().Where(s => s.BookId>0)
                        .Where(s => s.BookName == "C#").ToList();
    

11.2 LINQ to Object

  1. 定义武林高手类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace LINQ
    {
        /// <summary>
        /// 武林高手
        /// </summary>
        class MartialArtsMaster
        {
            /// <summary>
            /// 编号
            /// </summary>
            public int Id { get; set; }
            /// <summary>
            /// 名字
            /// </summary>
            public string Name { get; set; }
            /// <summary>
            /// 年龄
            /// </summary>
            public int Age { get; set; }
            /// <summary>
            /// 门派
            /// </summary>
            public string Menpai { get; set; }
            /// <summary>
            /// 功夫
            /// </summary>
            public string Kongfu { get; set; }
            /// <summary>
            /// 级别
            /// </summary>
            public int Level { get; set; }
    
            //无参构造
            public MartialArtsMaster()
            {
            }
            //有参构造
            public MartialArtsMaster(int id, string name, int age, string menpai, string kongfu, int level)
            {
                Id = id;
                Name = name;
                Age = age;
                Menpai = menpai;
                Kongfu = kongfu;
                Level = level;
            }
            //重写ToString方法
            public override string ToString()
            {
                return String.Format("Id:{0},Name:{1},Age:{2},Menpai:{3},Kongfu:{4},Level:{5}",Id,Name,Age,Menpai,Kongfu,Level);
            }
        }
    }
    
  2. 定义功夫类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace LINQ
    {
        /// <summary>
        /// 功夫
        /// </summary>
        class Kongfu
        {
            /// <summary>
            /// 功夫编号
            /// </summary>
            public int Id { get; set; }
            /// <summary>
            /// 功夫名字
            /// </summary>
            public string Name { get; set; }
            /// <summary>
            /// 功夫杀伤力
            /// </summary>
            public int Power { get; set; }
            //无参构造
            public Kongfu()
            {
            }
            //有参构造
            public Kongfu(int id, string name, int power)
            {
                Id = id;
                Name = name;
                Power = power;
            }
            //重写ToString方法
            public override string ToString()
            {
                return String.Format("Id:{0},Name:{1},Power:{2}", Id, Name, Power);
            }
        }
    }
    
  3. 初始化武林高手和功夫并进行查询

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace LINQ
    {
        class Program
        {
            static void Main(string[] args)
            {
                //初始化武林高手
                var masterList = new List<MartialArtsMaster>()
                {
                    new MartialArtsMaster(){Id = 1,Name = "黄蓉",Age = 18,Menpai = "丐帮" ,Kongfu = "打狗棒法",Level = 9},
                    new MartialArtsMaster(){Id = 2,Name = "洪七",Age = 70,Menpai = "丐帮" ,Kongfu = "打狗棒法",Level = 14},
                    new MartialArtsMaster(){Id = 2,Name = "洪七",Age = 70,Menpai = "丐帮" ,Kongfu = "降龙十八掌",Level = 14},
                    new MartialArtsMaster(){Id = 3,Name = "郭靖",Age = 20,Menpai = "丐帮" ,Kongfu = "降龙十八掌",Level = 14},
                    new MartialArtsMaster(){Id = 4,Name = "任我行",Age = 65,Menpai = "日月神教" ,Kongfu = "吸星大法",Level = 14},
                    new MartialArtsMaster(){Id = 5,Name = "东方不败",Age = 45,Menpai = "日月神教" ,Kongfu = "葵花宝典",Level = 13},
                    new MartialArtsMaster(){Id = 6,Name = "风清扬",Age = 99,Menpai = "华山" ,Kongfu = "独孤九剑",Level = 14},
                    new MartialArtsMaster(){Id = 7,Name = "梅超风",Age = 34,Menpai = "桃花岛" ,Kongfu = "九阴真经",Level = 9},
                    new MartialArtsMaster(){Id = 8,Name = "黄药师",Age = 67,Menpai = "桃花岛" ,Kongfu = "弹指神通",Level = 14}
                };
                //初始化武学
                var kongfuList = new List<Kongfu>()
                {
                    new Kongfu(){Id = 1,Name = "打狗棒法",Power =90 },
                    new Kongfu() { Id = 2, Name = "降龙十八掌", Power = 100 },
                    new Kongfu() { Id = 3, Name = "葵花宝典", Power = 110 },
                    new Kongfu() { Id = 4, Name = "独孤九剑", Power = 110 },
                    new Kongfu() { Id = 5, Name = "九阴真经", Power = 100 },
                    new Kongfu() { Id = 6, Name = "弹指神通", Power = 110 },
                    new Kongfu() { Id = 7, Name = "九阳真经", Power = 110 }
                };
    
                //1、查询所有武学级别大于12的武林高手
                //方法一:使用for循环进行查询
                //var res = new List<MartialArtsMaster>();
                //foreach (var masters in masterList)
                //{
                //    if(masters.Level > 12)
                //    {
                //        res.Add(masters);
                //    }
                //}
                //foreach(var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadKey();   //放在循环内需要手动回车去进行查看
    
                //方法二:使用LINQ进行查询(LINQ表达式写法)
                //from后面设置查询的集合,表示从哪个集合里面做查询
                //var res = from master in masterList  //from 元素 in 集合:master相当于masterList中的一个元素
                //          //where后面跟上查询的条件
                //          where master.Level > 12    //where 查询条件
                //          //select表示将查到的结果进行返回
                //          select master;             //select 元素
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadLine();
    
                //方法二:使用LINQ进行查询(扩展方法写法:使用Where方法,该方法传的是一个方法:方法的委托)
                //var res = masterList.Where(Func1);
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadLine();
    
                //将方法二转换成lambda表达式写法
                //var res = masterList.Where(master =>  master.Level > 12 );  //不需要返回值,会自动返回true
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadLine();
    
                //2、查询级别大于11且是丐帮的人
                //方法一:LINQ
                //var res = from master in masterList  //from 别名 in 集合
                //          where master.Level > 11  && master.Menpai == "丐帮"  //通过&&添加并列的条件
                //          select master;             //select 别名
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadLine();
    
                //将方法一转换成lambda表达式写法
                //var res = masterList.Where(master => master.Level > 11 && master.Menpai == "丐帮");  //不需要返回值,会自动返回true
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadLine();
    
                //3、联合查询
                //取得所学功夫的杀伤力大于100的大侠
                //方法一:LINQ表达式
                /*var res = from master in masterList
                          from kongfu in kongfuList
                          where master.Kongfu == kongfu.Name && kongfu.Power >100
                          //select new
                          //{
                          //    m = master,
                          //    k = kongfu
                          //};
                          select master;
                foreach (var master in res)
                {
                    Console.WriteLine(master);
                }
                Console.ReadLine();*/
    
                //方法二:扩展方法
                //masterList.SelectMany(kongfu => kongfuList,(master,kongfu)=>new {m=master,k=kongfu})
                //kongfu => kongfuList:第一个参数表示将masterList,kongfuList做一个联合查询
                //(master,kongfu)=>new {m=master,k=kongfu}):第二个参数表示一个组拼的结果,自定义 new {m=master,k=kongfu})一个新的对象进行返回
                //Where语句中的x参数表示 new { m = master, k = kongfu }这个对象
                //var res = masterList.SelectMany(kongfu => kongfuList, (master, kongfu) => new { m = master, k = kongfu })
                //                    .Where(x => x.m.Kongfu == x.k.Name && x.k.Power >100);
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadLine();
    
                //4、对查询结果进行排序 orderby(descending)
                //4.1 按单个条件进行排序
                //var res = from master in masterList  //from 别名 in 集合
                //          where master.Level > 8 && master.Menpai == "丐帮"  //通过&&添加并列的条件
                //          //orderby master.Age  //默认升序
                //          //orderby master.Age descending  //降序
                //          orderby master.Level, master.Age  //按照多个字段进行排序,如果字段的属性相同就按照第二个属性进行排序(默认升序)
                //          select master;             //select 别名
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadLine();
    
                //4.2 按照多个条件进行排序
                //var res = masterList.Where(master => master.Level > 8 && master.Menpai == "丐帮").OrderBy(master=>master.Age);  //按照年龄排序
                //var res = masterList.Where(master => master.Level > 8 ).OrderBy(master=>master.Level).OrderBy(master => master.Age);   //将按照等级排序的结果重新按照年龄排序
                //var res = masterList.Where(master => master.Level > 8 ).OrderBy(master=>master.Level).ThenBy(master => master.Age);   //先按照等级排序,在等级相同的情况下再按照年龄排序
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadKey();
    
                //5、join on集合联合查询
                //var res = from master in masterList
                //          join kongfu in kongfuList on master.Kongfu equals kongfu.Name
                //          where kongfu.Power > 100
                //          select new { m = master, k = kongfu };
    
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadKey();
    
                //6、分组查询 into groups (把武林高手按照所学的功夫分类,看一下哪个功夫修炼的人最多)
                //var res = from kongfu in kongfuList
                //          join master in masterList on kongfu.Name equals master.Kongfu
                //          into groups 
                //          orderby groups.Count() descending
                //          select new {k= kongfu,count=groups.Count()};
    
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadKey();
    
                //7、按照自身字段分组group
                //var res = from master in masterList
                //          //group master by master.Menpai into g   //按照门派分组
                //          group master by master.Kongfu into g     //按照功夫分组
                //          select new { count=g.Count(),key = g.Key };  //g.Key:表示按照哪个属性分的组
    
                //foreach (var master in res)
                //{
                //    Console.WriteLine(master);
                //}
                //Console.ReadKey();
    
                //8、量词操作符 any all
                //8.1 any:判断元素中有没有属于丐帮的人,只要有一个属于就返回true,否则false
                //bool res = masterList.Any(master => master.Menpai == "丐帮");    
                //Console.WriteLine(res); 
                //Console.ReadLine();
    
                //8.2 all:判断元素中是否全部属于丐帮的人,全部都是返回true,否则false
                bool res = masterList.All(master => master.Menpai == "丐帮");    
                Console.WriteLine(res);
                Console.ReadLine();
                
                //9、Select()
                //Func<MartialArtsMaster, MartialArtsMaster> func = m => m;
                //var query = masterList.Select(func);
                //等价于:s => s 表示传入一个MartialArtsMaster参数,返回一个MartialArtsMaster对象
                //var query = masterList.Select(m => m);
    
                //匿名类:没有名字的类
                //var n = new { id = 1 ,s = 5};
                //生成一个以id、name为属性的新结果集
                //var query = masterList.Select(m => new
                //{
                //    id = m.Id,
                //    name = m.Name,
                //});
    
                //可对结果集进行条件限制和修改
                //var query = masterList.Select(m => new
                //{
                //    id = m.Id,
                //    name = m.Age>50 ? 70 : 20
                //});
    
                //可进行数据传递:初始化两个类型,将第一个类型赋值给第二个类型
                //var query = masterList.Select(m => new StudentModel
                //{
                //    Id = m.Id,
                //    Name = m.Name
                //});
            }
            //Where委托的过滤方法
            static bool Func1(MartialArtsMaster master)
            {
                if (master.Level > 12) return true;
                return false;
            }
            static bool Func2(MartialArtsMaster master,Kongfu kongfu)
            {
                if (master.Level > 12 && kongfu.Power>100) return true;
                return false;
            }
        }
    }
    

11.3 LINQ to Mysql

11.3.1 C#连接Msyql数据库

1、准备工作

  1. 打开Nuget程序包

    image-20220927095810247

  2. 安装Mysql.Data(这里已安装)

    image-20220927095933243

  3. 数据库构建

    /*
     Navicat Premium Data Transfer
    
     Source Server         : fa
     Source Server Type    : MySQL
     Source Server Version : 50727
     Source Host           : localhost:3306
     Source Schema         : mybatis
    
     Target Server Type    : MySQL
     Target Server Version : 50727
     File Encoding         : 65001
    
     Date: 27/09/2022 16:18:25
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for user
    -- ----------------------------
    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user`  (
      `id` int(10) NOT NULL COMMENT '用户id',
      `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
      `pwd` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of user
    -- ----------------------------
    INSERT INTO `user` VALUES (1, '小明', '123123');
    INSERT INTO `user` VALUES (2, '小雨', '654321');
    INSERT INTO `user` VALUES (3, '王五', '123456');
    INSERT INTO `user` VALUES (4, '小舒', '123456');
    
    SET FOREIGN_KEY_CHECKS = 1;
    

2、数据库连接查询相关操作

using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MySQLTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //1、创建连接并执行简单查询
            //Database=要访问的数据库;Datasource=要连接的主机;port=端口号;Userid=mysql用户名;Password=mysql密码
            //string connStr = "Database=mybatis;Datasource=127.0.0.1;port=3306;Userid=root;Password=123456";
            新建mysql的连接
            //MySqlConnection conn = new MySqlConnection(connStr);

            //conn.Open();//进行连接

            //string sql = "select * from user where id=3";
            MySqlCommand cmd = new MySqlCommand(sql, conn);//mysql命令

            //MySqlCommand cmd = new MySqlCommand(sql, conn);//mysql命令

            ExecuteScalar():返回结果集中的第一行的第一列,使用ExecuteScalar()获取指定数据表中的数据数量
            //int i = Convert.ToInt32(cmd.ExecuteScalar());
            //Console.WriteLine("查到的数据数量为" + i + "条");

            //MySqlDataReader reader = cmd.ExecuteReader();//执行查询语句

            //if (reader.HasRows)//判断是否有数据
            //{
            //    reader.Read();//读取数据
            //    string id = reader.GetString("id");//通过列名获得数据
            //    string username = reader.GetString("name");//通过列名获得数据
            //    string password = reader.GetString("pwd");//通过列名获得数据

            //    Console.WriteLine("id:" + id);
            //    Console.WriteLine("username:" + username);
            //    Console.WriteLine("password:" + password);
            //}
            //Console.ReadKey();
            //reader.Close();

            //关闭连接
            //conn.Close();

            //2、创建连接查询全部数据,并打印到控制台
            //Database=要访问的数据库;Datasource=要连接的主机;port=端口号;Userid=mysql用户名;Password=mysql密码
            //以下三种均可:不区分大小写
            //string connStr = "Database = mybatis; DataSource = 127.0.0.1; port = 3306; uid = root; pwd = 123456";
            //string connStr = "Database = mybatis; DataSource=127.0.0.1; port=3306;UserId=root; Password=123456";
            //string connStr = "Database = mybatis; Datasource=127.0.0.1; port=3306;Userid=root; Password=123456";
            新建mysql的连接
            //MySqlConnection conn = new MySqlConnection(connStr);

            //conn.Open();//进行连接

            //string sql = "SELECT * FROM `user`";
            //MySqlCommand cmd = new MySqlCommand(sql, conn);//mysql命令

            //MySqlDataReader reader = cmd.ExecuteReader();
            //while (reader.Read())
            //{
            //    //第一种遍历方法:
            //    //GetString(0):返回id列的值  reader.GetString(1):返回name列的值 reader.GetString(2):返回pwd列的值
            //    //Console.WriteLine(reader.GetString(0)+" "+reader.GetString(1) + " " + reader.GetString(2));

            //    //第二种遍历方法:
            //    //GetString("id"):返回id列的值  reader.GetString("name"):返回name列的值 reader.GetString("pwd"):返回pwd列的值
            //    Console.WriteLine(reader.GetString("id") + " " + reader.GetString("name") + " " + reader.GetString("pwd"));
            //}

            //Console.ReadLine();

            //reader.Close();
            关闭连接
            //conn.Close();

            //3、查询数据表中一共有多少条数据?
            //string connStr = "Database = mybatis; Datasource = 127.0.0.1; port = 3306; uid = root; pwd = 123456";
            //MySqlConnection conn = new MySqlConnection(connStr);
            //conn.Open();

            string sql = "select count(id) from user";  //查询指定列的数据总量
            //string sql = "select count(*) from user";     //查询所有列的数据总量
            //MySqlCommand cmd = new MySqlCommand(sql, conn);
            设置CommandType属性为Text,使其只执行SQL语句文本形式
            cmd.CommandType = CommandType.Text;

            //int i = Convert.ToInt32(cmd.ExecuteScalar());
            //Console.WriteLine("共有" + i + "条数据");
            //Console.ReadLine();

            //conn.Close();

            //4、填充DataSet数据集
            //string connStr = "Database = mybatis; Datasource = 127.0.0.1;uid = root;pwd = 123456";
            //MySqlConnection conn = new MySqlConnection(connStr);
            //conn.Open();

            //string sql = "select * from user";
            //MySqlCommand cmd = new MySqlCommand(sql, conn);
            //cmd.CommandType = CommandType.Text;

            创建一个MySqlDataAdapter对象
            //MySqlDataAdapter adapter = new MySqlDataAdapter();
            //adapter.SelectCommand = cmd;
            创建一个DataSet对象
            //DataSet ds = new DataSet();

            Fill:用于填充 DataSet中的表行的数据
            //adapter.Fill(ds);

            Console.WriteLine(ds.Tables[0].Rows.Count);  //获取行的总的数量
            Console.WriteLine(ds.Tables[0].Columns.Count);  //获取列的总的数量
            //var dt = ds.Tables[0];

            遍历表,查询表中所有数据
            //for (var i = 0; i < dt.Rows.Count; i++)
            //{
            //    var row = dt.Rows[i];
            //    var id = row[0].ToString();
            //    var name = row[1].ToString();
            //    var pwd = row[2].ToString();
            //    Console.WriteLine(id +" "+ name +" "+ pwd);
            //}
            //Console.ReadLine();

            //conn.Close();
        }
    }
}
11.3.2 DBFirst(EFMysql)
//EFcore提供程序(dll):使用不同的数据库连接需要导入不同的包
//Microsoft SQL Server 2012 or later     Microsoft.EntityFrameworkCore.SqlServer
//sQLite 3.7 or later                    Microsoft.EntityFrameworkCore.SQLite
//MySQL                                  MySQL.Data.EntityFrameworkCore、Pomelo.EntityFrameworkCore.MySql
//In-memory                              Microsoft.EntityFrameworkCore.InMemory(性能测试)
//Azure Cosmos DB SQL API                Microsoft. EntityFrameworkCore.Cosmos
//0racle DB 11.2                         Oracle.EntityFrameworkCore

DBFirst:EF(Entity Framework) 通过DBFirst方式连接Mysql数据库并迁移生成相应的类到项目文件下

  1. 创建.Net Core项目

    image-20220928185544844

    image-20220928185616012

    注:控制台应用程序也可以

    image-20220928192049223

    image-20220928192117623

  2. 利用Nuget添加以下引用

    MySql.Data.EntityFrameworkCore
    Pomelo.EntityFrameworkCore.MySql
    Microsoft.EntityFrameworkCore.Tools
    

    image-20220928185738092

  3. 打开程序包管理控制台,执行语句

    image-20220928185832621

    //1、简单生成命令
    Scaffold-DbContext "Server=127.0.0.1;UserId=root;Password=123456;Database=mybatis" Pomelo.EntityFrameworkCore.MySql -o Models -f
    //注:Password需要填写自己的密码
    
    //2、复杂生成命令
    //-Context MyContext        上下文类名称
    //-outputdir Models         模型输出目录
    //-contextdir Data          上下文目录
    //-Tables blog,user         指定生成的模型(指定生成数据库中的哪张表)
    //-DataAnnotations          生成数据注解
    //-Force                    强制覆盖生成
    
    Scaffold-DbContext "Server=127.0.0.1;UserId=root;Password=123456;Database=mybatis" Pomelo.EntityFrameworkCore.MySql -Context MyContext -outputdir Models -contextdir Data -Tables blog -DataAnnotations -Force
    
  4. 即可连接成功,成功之后会自动生成如下内容

    image-20220928190114860

  5. 连接成功之后即可用LINQ对数据库进行查询

    using EFMysql.Models;
    
    //mybatisContext:就代表一个数据库(mybaits是我在mysql中的数据库名字,mybatisContext)
    mybatisContext mybatisContext = new mybatisContext();  //mybatisContext需要释放
    
    //所有的表的后缀都被加了一个s,如果用mybatisContext.表名则会访问不到,需要用mybatisContext.Users去访问表
    var query = mybatisContext.Users.Where(s => s.Id > 2);
    foreach (var user in query)
    {
        //遍历User表中id>2的人的名字
        Console.WriteLine(user.Name);
    }
    mybatisContext.Dispose();
    

    image-20220929105429397

    注意:

    MyContext db = new MyContext();  //MyContext都要释放(像流一样需要关闭,如果不关闭就需要用using语句块)
    db.Dispose();
    
    using(MyContext db = new MyContext())
    {
       ...
    }
    

    image-20220930173512479

  6. 查询学生表中id>0且名字包含“王”的人

    using EFMysql.Models;
    
    //mybatisContext:就代表一个数据库(mybaits是我在mysql中的数据库名字,mybatisContext)
    mybatisContext db = new mybatisContext();
    
    var query = db.Students.Where(s => s.Id > 0).Where(s => s.Name.Contains("王"));
    foreach (var student in query)
    {
        Console.WriteLine(student.Id + " " + student.Name);
    }
    db.Dispose();
    

    image-20220929111325731

7、Where()、Select()扩展方法的使用

using EFMysql.Models;

//mybatisContext:就代表一个数据库(mybaits是我在mysql中的数据库名字,mybatisContext)
mybatisContext db = new mybatisContext();

//1、查询用户表中id>2的人的名字
//所有的表的后缀都被加了一个s,如果用db.表名则会访问不到,需要用mybatisContext.Users去访问表
//var query = db.Users.Where(s => s.Id > 2);

//遍历id>2的人的名字
//foreach (var user in query)
//{
//    Console.WriteLine(user.Name + " " + user.Pwd);
//}

//2、查询学生表中id>3且名字中带有“王”字的人
//var query = db.Students.Where(s => s.Id > 3).Where(s => s.Name.Contains("王"));
//foreach (var student in query)
//{
//    Console.WriteLine(student.Id + " " + student.Name);
//}

//3、查询学生表中id>0且名字中带有“王”字的人,将其赋值给User表,密码设置为123456
var query = db.Students.Where(s => s.Id > 0).Where(s => s.Name.Contains("王")).Select(m => new User
{
    Id = m.Id,
    Name = m.Name,
    Pwd = "123456"
});
foreach (var user in query)
{
    Console.WriteLine(user.Id + " " + user.Name+" "+ user.Pwd);
}

db.Dispose();
11.3.2 CodeFirst

CodeFirst:EF(Entity Framework) 通过CodeFirst方式迁移生成数据库表

注意:因为是CodeFirst,意味着是先有代码再有数据库表,所以要在mysql数据库中新建一个空数据库来存放数据库表

  1. 新建数据库上下文类

    using CodeFirst.Models;
    using Microsoft.EntityFrameworkCore;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace CodeFirst
    {
        /// <summary>
        /// 数据库上下文类
        /// </summary>
        public class MyContext : DbContext
        {
            //主要是框架在用,有时候用户也可能用到
            public MyContext()
            {
    
            }
            //DbContextOptions:选项模式,实现参数动态化
            public MyContext(DbContextOptions<MyContext> options) : base(options)
            {
    
            }
            /// <summary>
            /// Blog数据表
            /// </summary>
            public DbSet<Blog> Blogs { get; set; }  //这个属性叫什么名字,数据库中的表就叫什么名字
    
            /// <summary>
            /// 配置上下文类
            /// </summary>
            /// <param name="optionsBuilder">选项生成器</param>
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                //数据库连接字符串包括:
                //server:服务器(127.0.0.1:表示本机,localhost)
                //database:mysql中的数据库名称,因为是CodeFirst所以要在mysql数据库中新建一个空数据库
                //port:数据库端口号
                //Userid:数据库用户名
                //Password:数据库密码
                string connStr = @"server=127.0.0.1;database=codefirst;port=3306;Userid=root;Password=123456";
                //"5.7.27-mysql":Mysql数据库版本
                optionsBuilder.UseMySql(connStr, Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.7.27-mysql"));
            }
        }
    }
    
  2. 为需要在codefirst数据库中建立的Blog表新建一个类,类名:表名 属性:字段名

    using System;
    using System.Collections.Generic;
    
    namespace EFMysql.Models
    {
        public partial class Blog
        {
            /// <summary>
            /// 博客id
            /// </summary>
            public string Id { get; set; } = null!;
            /// <summary>
            /// 博客标题
            /// </summary>
            public string Title { get; set; } = null!;
            /// <summary>
            /// 博客作者
            /// </summary>
            public string Author { get; set; } = null!;
            /// <summary>
            /// 创建时间
            /// </summary>
            public DateTime CreateTime { get; set; }
            /// <summary>
            /// 浏览量
            /// </summary>
            public int Views { get; set; }
        }
    }
    

    image-20220930112504480

  3. 数据库迁移(Code First模式)

    1)它有两个命令:

    • add-migration:启动迁移,生成迁移的代码

    • init-Mig(迁移的名称,可以自定义,随便写):初始化迁移

      add-migration inin-Mig  //两个命令通常放在一起执行
      
    • update-database:执行生成数据库表的操作

    2)迁移命令在程序包管理控制台执行

    image-20220930103323692

    3)执行命令之前,需添加以下Nuget包(注意:不加会报错)

    image-20220930104637601

    image-20220930105039378

    4)执行命令,生成Migrations文件夹

    add-migration inin-Mig
    

    image-20220930104224197

    可以撤销以上操作:

    //To undo this action, use Remove-Migration.
    Remove-Migration
    
  4. 注意一下init-Mig类中的Down方法

            //如果使用DB First则需要将此方法注释或者删除,因为它会删除表(无法撤销,删除了就没有了!!!)
            protected override void Down(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.DropTable(
                    name: "Blogs");
            }
    

    image-20220930105957521

  5. 执行update-database命令

    update-database
    

    image-20220930112752002

十二、线程、任务和同步

线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。

线程是轻量级进程。一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。

到目前为止我们编写的程序是一个单线程作为应用程序的运行实例的单一的过程运行的。但是,这样子应用程序同时只能执行一个任务。为了同时执行多个任务,它可以被划分为更小的线程。

12.1 线程生命周期

线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。

下面列出了线程生命周期中的各种状态:

  • 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。

  • 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。

  • 不可运行状态

    下面的几种情况下线程是不可运行的:

    • 已经调用 Sleep 方法
    • 已经调用 Wait 方法
    • 通过 I/O 操作阻塞
  • 死亡状态:当线程已完成执行或已中止时的状况。

在 C# 中,System.Threading.Thread 类用于线程的工作。它允许创建并访问多线程应用程序中的单个线程。进程中第一个被执行的线程称为主线程(main方法)。

当 C# 程序开始执行时,主线程自动创建。使用 Thread 类创建的线程被主线程的子线程调用。您可以使用 Thread 类的 CurrentThread 属性访问线程。

下面的程序演示了线程的执行:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace thread
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //线程
            //Thread t = Thread.CurrentThread;
            //t.Name = "MainThread";
            //Console.WriteLine(t.Name);
            //Console.ReadKey();

            //委托:将方法当做数据进行传递
            //ThreadStart threadStart = new ThreadStart(ChildThread);
            //Thread childThread = new Thread(threadStart);

            Thread childThread = new Thread(new ThreadStart(ChildThread));
            //主线程和子线程不是一个在一个线程里边运行(是两个不同的线程),所以执行顺序是不确定的,由CPU调度决定
            childThread.Start();  //子线程
            //Console.WriteLine("MainThread Method");  //主线程

            Thread.Sleep(1000);
            childThread.Abort();   //强制终止线程

            while (isRun)
            {
                Console.WriteLine("MainThread - 聊天中...");
                Thread.Sleep(500);  //500ms
                isRun = false;
            }
        }
        private static bool isRun = true;    //较于Abort()温和一点,定义了一个isRun方法,可以人为控制线程的终止
        private static void ChildThread()
        {
            while (isRun)
            {
                Console.WriteLine("ChildThread - 听歌中...");
                Thread.Sleep(500);  //500ms
            }
        }
    }
}

线程的同步和异步执行

  • 同步执行:在一个线程里面执行
  • 异步执行:在已有一个线程运行的基础上,再开一个线程运行(多线程)

12.2 异步委托

创建线程的一种简单方式是定义一个委托,并异步调用它。委托是方法类型的安全引用。

Delegate类 还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。接下来,定义一个方法,使用异步委托调用(开启一个线程去执行这个方法)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncDelegate
{
    internal class Program
    {
        static void Main(string[] args)
        {
            TestDelegate testDelegate = Test;

            //testDelegate();   //直接调用就相当于同步执行
            testDelegate.BeginInvoke(null,null);
            Console.WriteLine("Main Completed!");
            Console.ReadLine();
        }
        static void Test()
        {
            Console.WriteLine("Test Started!");
            Console.WriteLine("Test Running!");

            Thread.Sleep(3000);

            Console.WriteLine("Test Completed!");
        }
        delegate void TestDelegate();
    }
}

12.3 Thread类启动线程和数据传输

使用Thread类可以创建和控制线程,Thread构造函数的参数是一个无参无返回值的委托类型。

namespace System.Threading
{
    [ComVisible(true)]
    public delegate void ThreadStart();
}

使用Thread启动线程和数据传输

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Thread01
{
    internal class Program
    {
        //如果要传输多个数据需要声明一个结构体
        public struct Data
        {
            public string message;
            public int age;
        }
        static void Test()
        {
            Console.WriteLine("Started");
            Thread.Sleep(1000);
            Console.WriteLine("Completed");
            Console.ReadLine();
        }
        static void Download(Object obj)
        {
            //as:做一个类型转换,转换成功返回,否则返回null
            //var str = obj as string;
            //Console.WriteLine(str);

            //转换成对应的结构体,结构体不能为null,所以不能使用as进行转换,此处需要使用强制类型转换
            Data data = (Data)obj;
            Console.WriteLine(data.age);
        }
        static void Main(string[] args)
        {
            //启动线程
            //Thread t = new Thread(Test);
            //Thread t = new Thread(()=>Console.WriteLine("Child Thread:"+Thread.CurrentThread.ManagedThreadId));
            //t.Start();
            //Console.WriteLine("Main Completed:"+Thread.CurrentThread.ManagedThreadId);
            //Console.ReadLine();

            //数据传输
            //Thread thread = new Thread(Download);
            //thread.Start("http://www.xxx.com/xx/xx/xxx.mp4");
            //Console.WriteLine("Main Completed:"+Thread.CurrentThread.ManagedThreadId);
            //Console.ReadKey();

            //多个数据传输
            Data data = new Data();
            data.message = "";
            data.age = 12;
            Thread thread = new Thread(Download);
            thread.Start(data);
            Console.WriteLine("Main Completed:" + Thread.CurrentThread.ManagedThreadId);
            Console.ReadKey();
        }
    }
}

12.4 自定义传递数据和前后台线程

自定义类传递数据

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Thread02
{
    internal class DownloadUntils
    {
        public string URL { get; private set; }
        public string Message { get; private set; }

        public DownloadUntils(string uRL, string message)
        {
            URL = uRL;
            Message = message;
        }

        public DownloadUntils()
        {
        }

        public void Download()
        {
            Console.WriteLine("从" + URL + "下载中 ...");
            Console.ReadLine();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Thread02
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var downloadUntils = new DownloadUntils("http://www.xxx.xxx/xx","xiaoshu");
            Thread t = new Thread(downloadUntils.Download);
            t.Start();
        }
    }
}

后台线程和前台线程

只有一个前台线程在运行,应用程序的进程就在运行,如果有多个前台线程在运行,但是Main方法结束了,应用程序的进程仍然是运行的,直到所有的前台线程完成任务为止。

在默认情况下,用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。

在Thread类创建线程的时候,可以设置IsBackGround属性,表示它是一个前台线程还是一个后台线程。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Thread03
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //var t = new Thread(ThreadMain) { IsBackground = false};  //如果此线程为或将为后台线程则为true,否则为flase
            var t = new Thread(ThreadMain) { IsBackground = true};  //如果此线程为或将为后台线程则为true,否则为flase
            t.Start();
            Console.WriteLine("Main thread completed now.");
            Console.ReadLine();

            //初始化对象的两种方式
            Student student1 = new Student(20,"xiaoshu");  //调用有参构造
            Student student2 = new Student() { Age = 20, Name = "小舒" };  //调用无参构造
        }
        static void ThreadMain()
        {
            Console.WriteLine("Thread:"+Thread.CurrentThread.Name+" started");
            Thread.Sleep(3000);
            Console.WriteLine("Thread:"+Thread.CurrentThread.Name+" completed");
            Console.ReadKey();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Thread03
{
    internal class Student
    {
        public int Age { get; set; }
        public string Name { get; set; }

        //无参构造
        public Student()
        {
        }
        //有参构造
        public Student(int age, string name)
        {
            Age = age;
            Name = name;
        }
    }
}

12.5 线程优先级和线程的状态

线程的优先级

线程由操作系统进行调度,一个CPU同一时间只能做一件事情(运行一个线程中的计算任务),当有很多线程需要CPU去执行的时候,线程调度器会根据线程的优先级去判断先去执行哪一个线程,如果优先级相同的话,就使用一个循环调度规则,逐个执行每个线程。

在Thread类中,可以设置Priority属性,以影响线程的基本优先级,Priority属性是一个ThreadPriority枚举定义的一个值。定义的级别有Highest,AboveNormal,Normal,BelowNormal和Lowest。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Thread04
{
    internal class Program
    {
        static void A()
        {
            while (true)
            {
                Console.Write("A");
            }
        }
        static void B()
        {
            while (true)
            {
                Console.Write("B");
            }
        }
        static void Main(string[] args)
        {
            Thread a = new Thread(A);
            Thread b = new Thread(B);

            a.Priority = ThreadPriority.Highest;  //优先级高表示可以抢占到更多的CPU资源,并不意味着执行的次数就多,与其他线程还是同步交替执行
            a.Priority = ThreadPriority.Lowest; 

            a.Start();
            b.Start();
        }
    }
}

线程的状态

Thread.Start():启动线程的执行;

Thread.Suspend():挂起线程,或者如果线程已挂起,则不起作用;

Thread.Resume():继续已挂起的线程;

Thread.Interrupt():中止处于Wait或者Sleep或者Join 线程状态的线程;

Thread.Join():阻塞调用线程,直到某个线程终止时为止

Thread.Sleep():将当前线程阻塞指定的毫秒数;

Thread.Abort():以开始终止此线程的过程。如果线程已经在终止,则不能通过Thread.Start()来启动线程。

image-20220920121246515

12.6 线程池

创建线程需要时间。如果有不同的小任务要完成,就可以实现创建许多线程,在应完成这些任务是发出请求。这个线程数最好在需要更多的线程是增加,在需要释放资源时减少。不需要自己创建线程池,系统已经有一个ThreadPool类管理线程。这个类会在需要时减少增减池中的线程数,直到达到最大的线程数。池中的最大线程数是可配置的。在双核CPU中,默认设置为1023个工作线程和1000个I/O线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。如果有更多的作业需要处理,线程池中的个数也到达了极限,最新的作业就要排队,且必须等待线程完成其任务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Thread05
{
    internal class Program
    {
        static void Main(string[] args)
        {
            for(int i = 0; i < 10; i++)
            {
                //线程池中的所有线程都是后台线程
                ThreadPool.QueueUserWorkItem(Download);
                Thread.Sleep(500);
                Console.ReadLine();
            }
        }
        static void Download(Object state)
        {
            for(int i = 0; i <3; i++)
            {
                Console.WriteLine("Downloading ...:"+Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(50);
            }
        }
    }
}

12.7 任务

任务和线程的区别:
1、任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行。

2、任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。
3、Task的优势:
  ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:
  ThreadPool不支持线程的取消、完成、失败通知等交互性操作;
  ThreadPool不支持线程执行的先后次序;
  以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在,微软提供了一个功能更强大的概念:Task。Task在线程池的基础上进行了优化,并提供了更多的API。在Framework 4.0中,如果我们要编写多线程程序,Task显然已经优于传统的方式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskStudy
{
    internal class Program
    {
        static void Test()
        {
            for(int i = 0; i < 10; i++)
            {
                Console.WriteLine("A");
            }
            Console.ReadLine();
        }
        static void FirstDownlad()
        {
            Console.WriteLine("Downloading");
            Thread.Sleep(3000);
        }
        static void SecondAlert(Task task)
        {
            Console.WriteLine("下载完成了");
        }
        static void Main(string[] args)
        {
            //启动任务的第一种方法:
            //TaskFactory taskFactory = new TaskFactory();
            //taskFactory.StartNew(Test);
            //Thread.Sleep(5000);

            //第二种方法
            //Task task = new Task(Test);
            //task.Start();
            //Thread.Sleep(1000);

            //ContinueWith:就是在第一个Task完成后自动启动下一个Task,实现Task的延续
            Task task1 = new Task(FirstDownlad);
            Task task2 = task1.ContinueWith(SecondAlert);
            task1.Start();
            Console.ReadKey();
        }
    }
}

12.8 资源访问冲突和死锁问题

资源访问冲突

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskStudy
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //资源访问冲突,可能出现,读写不一致时出现
            var stateObject = new StateObject();
            for(int i = 0; i < 100; i++)
            {
                //不加锁state可能被打印出5,加锁就不会
                Thread thread = new Thread(stateObject.ChangeState);
                thread.Start();
            }
        }
    }
    class StateObject
    {
        //声明锁
        private Object _lock = new Object();
        private int state1 = 5;
        public void ChangeState()
        {
            //拿到锁将可能出现资源冲突的代码块添加上锁,锁只会被一个线程拿到,不可能被多个线程拿到
            //锁优点:解决资源冲突问题
            //锁缺点:同一时间只能被同一个线程执行被加锁的代码块,可能会拖慢多线程执行的速度
            lock (_lock) { 
            if (state1 == 5)
            {
                state1++;  
                Console.WriteLine("State1:" + state1+"---->"+Thread.CurrentThread.ManagedThreadId);  //读
                Console.ReadLine() ;
            }
            state1 = 5;  //写
        }
    }
}

死锁问题

以下代码可能出现死锁

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskStudy
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var stateObject = new StateObject();
            for(int i = 0; i < 2; i++)
            {
                //Thread thread = new Thread(stateObject.ChangeState1);
                //thread.Start();
                Thread thread1 = new Thread(stateObject.ChangeState1);
                thread1.Start();
                Thread thread2 = new Thread(stateObject.ChangeState2);
                thread2.Start();
            }
            Console.ReadLine();
        }
    }
    class StateObject
    {
        //声明锁
        private Object _lock1 = new Object();
        private Object _lock2 = new Object();
        private int state1 = 5;
        private int state2 = 5;
        public void ChangeState1()
        {
            lock (_lock1) {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第一把锁");
                lock (_lock2) {
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第二把锁");
                    if (state1 == 5)
                    {
                        state1++;
                        Console.WriteLine("State1:" + state1 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state1 = 5;  //写

                    if (state2 == 5)
                    {
                        state2++;
                        Console.WriteLine("State2:" + state2 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state2 = 5;
                }
             }
        }
        public void ChangeState2()
        {
            lock (_lock2)
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第二把锁");
                lock (_lock1)
                {
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第一把锁");
                    if (state1 == 5)
                    {
                        state1++;
                        Console.WriteLine("State1:" + state1 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state1 = 5;  

                    if (state2 == 5)
                    {
                        state2++;
                        Console.WriteLine("State2:" + state2 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state2 = 5;
                }
            }
        }
    }
}

解决:定义拿锁的顺序,都先拿第一把再拿第二把

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskStudy
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //资源访问冲突,可能出现,读写不一致时出现
            var stateObject = new StateObject();
            for(int i = 0; i < 2; i++)
            {
                //不加锁state可能被打印出5,加锁就不会
                //Thread thread = new Thread(stateObject.ChangeState1);
                //thread.Start();
                Thread thread1 = new Thread(stateObject.ChangeState1);
                thread1.Start();
                Thread thread2 = new Thread(stateObject.ChangeState2);
                thread2.Start();
            }
            Console.ReadLine();
        }
    }
    class StateObject
    {
        //声明锁
        private Object _lock1 = new Object();
        private Object _lock2 = new Object();
        private int state1 = 5;
        private int state2 = 5;
        public void ChangeState1()
        {
            //拿到锁将可能出现资源冲突的代码块添加上锁,锁只会被一个线程拿到,不可能被多个线程拿到
            //锁优点:解决资源冲突问题
            //锁缺点:同一时间只能被同一个线程执行被加锁的代码块,可能会拖慢多线程执行的速度
            lock (_lock1) {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第一把锁");
                lock (_lock2) {
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第二把锁");
                    if (state1 == 5)
                    {
                        state1++;
                        Console.WriteLine("State1:" + state1 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state1 = 5;  //写

                    if (state2 == 5)
                    {
                        state2++;
                        Console.WriteLine("State2:" + state2 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state2 = 5;
                }
             }
        }
        public void ChangeState2()
        {
            lock (_lock1)
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第一把锁");
                lock (_lock2)
                {
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "拿到了第二把锁");
                    if (state1 == 5)
                    {
                        state1++;
                        Console.WriteLine("State1:" + state1 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state1 = 5;  

                    if (state2 == 5)
                    {
                        state2++;
                        Console.WriteLine("State2:" + state2 + "---->" + Thread.CurrentThread.ManagedThreadId);  //读
                    }
                    state2 = 5;
                }
            }
        }
    }
}

十三、流

什么是流

  • 流是一种数据的处理格式
  • 如果数据从外部源传输到程序中,这就是读取流;局部读取,并不是全部加载到内存中
  • 如果数据从程序传输到外部源中,这就是写入流

外部源可能是

  • 一个文件
  • 网络上的数据
  • 内存区域上
  • 读写到命名管道上

为什么使用流处理数据?

  • 数据小,可以直接一次性进行搬运;数据大,可以把数据当做水,接一个水管,一点一点搬运

  • 读写内存使用System.IO.MemorySystem

  • 处理网络数据使用System.Net.Sockets.NetworkStream

13.1 读取流和写入流(输入流和写入流)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Streamstudy
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var before = DateTime.Now;
            //读取流
            FileStream reader = new FileStream(@"E:\Snipaste截屏.zip",FileMode.Open,FileAccess.Read)
            //写入流
            FileStream writer = new FileStream(@"E:\Snipaste截屏Copy.zip",FileMode.Create,FileAccess.Write);

            //1、实现边读边写:一次读一个字节
            //int nextByte = -1;   //读取到的下一个字节
            //while ((nextByte = reader.ReadByte()) != -1)  //当它逐个读完的时候,最后返回-1,循环结束
            //{
            //    writer.WriteByte((byte)nextByte);
            //}
            //Console.WriteLine("复制成功");

            先开的流后关闭
            //writer.Close();
            //reader.Close();

            //var after = DateTime.Now;
            时间相减
            //var time = after.Subtract(before);
            //Console.WriteLine(time.TotalMilliseconds); //得到总的毫秒数:166.9271
            //Console.ReadLine();

            //2、实现边读边写:一次读取102个字节(1024可以自定义,这里设置成一次读取1024字节,也可以设置成其他)
            byte[] buffer = new byte[1024];
            int count;

            while ((count = reader.Read(buffer,0,1024)) != 0)  //Read方法,如果读到流的末尾会返回零
            {
                writer.Write(buffer, 0, count);
            }

            Console.WriteLine("复制成功");

            //先开的流后关闭
            writer.Close();
            reader.Close();

            var after = DateTime.Now;
            //时间相减
            var time = after.Subtract(before);
            Console.WriteLine(time.TotalMilliseconds); //得到总的毫秒数:56.6668
            Console.ReadLine();
        }
    }
}

13.2 读取和写入文本文件

读取文本文件流

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StreamReaderAndWriter
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string sourceFile = @"E:\诗词清单.txt";

            StreamReader reader = new StreamReader(sourceFile);
            //另外的创建方式1:
            //FileStream fs = new FileStream(sourceFile,FileMode.Open);  //将文件变成流的形式
            //StreamReader reader = new StreamReader(fs);
            //另外的创建方式2:
            //FileInfo fileInfo = new FileInfo(sourceFile);
            //fileInfo.OpenText();  //打开文本读取流

            //读取第一行
            //var line = reader.ReadLine(); 
            //Console.WriteLine(line);
            //reader.Close();
            //Console.ReadLine();

            //读取全部文本内容
            //方式1:
            //string line = null;
            //while ((line = reader.ReadLine()) != null)
            //{
            //    Console.WriteLine(line);
            //}
            //reader.Close();  //关闭流

            //方式二:
            var str = reader.ReadToEnd();
            Console.WriteLine(str);
            reader.Close();

            Console.ReadLine();
        }
    }
}

写入文本文件流

StreamWriter的创建
1、StreamWriter sw = new StreamWriter(@"c:xx.txt");(默认使用UTF-8编码)

2、StreamWriter sw = new StreamWriter(@"c:xx.txt",true,Encoding.ASCII)
第二个参数表示是否以追加的方式打开,第三个参数是编码方式

3、通过FileStream创建StreamWriter
FileStream fs = new FileStream(@"c : \xx\xx.txt" ,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
StreamWriter SW=New StreamWriter(Fs);

4、通过Filelnfo创建StreamWriter
Filelnfo myFile = new Filelnfo(@"c : \xx\xx.txt");
StreamWriter sw = myFile.CreateText() ;
所有流用完之后关闭sw.Close() ;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StreamReaderAndWriter
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string sourceFile = @"E:\诗词清单.txt";
            string targetFile = @"E:\诗词清单Copy.txt";

            StreamReader reader = new StreamReader(sourceFile);
            StreamWriter writer = new StreamWriter(targetFile,true);

            string line = null;

            while((line = reader.ReadLine()) != null)
            {
                writer.WriteLine(line);
            }
            Console.WriteLine("写入成功");
            writer.Close();
            reader.Close();

            Console.ReadLine();
        }
    }
}

十四、泛型使用

14.1 泛型

泛型定义

泛型是可以当做任意一种且由编译期间决定其最终类型的数据类型。通俗来讲,泛型,泛指某种类型。

泛型作用

  • 泛型增强了代码的可读性
  • 泛型有助于实现代码的重用、保护类型的安全以及提高性能
  • 我们可以创建泛型集合类
  • 泛型实现了类型和方法的参数化
  • 我们还可以对泛型类进行约束以访问特定数据类型的方法
  • 关于泛型数据类型中使用的类型的信息可在运行时通过反射获取

泛型声明

泛型属性:

public class GenericClass<T>
    {
        /// <summary>
        /// 泛型属性
        /// </summary>
        public T ItemName { get; set; }
        public string MyName { get; set; }   //也可定义其他属性
    }

泛型类:将指定类型参数(Type Parameter,通常以T表示),紧随在类名后面,并包含在<>符号内

    /// <summary>
    /// 分页对象
    /// </summary>
    public class PageModel<T>
    {
        public List<T> Data { get; set; }
        public int Total { get; set; }
    }

泛型方法:将指定类型参数(Type Parameter,通常以T表示),紧随在方法名后面,并包含在<>符号内

1、格式

访问修饰符 方法返回类型 方法名<T>(参数列表)
{
    //方法体...
}

2、普通类中的泛型方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Generic
{
    public class MyClass
    {
        /// <summary>
        /// 普通类中的泛型方法
        /// </summary>
        /// <typeparam name="T">返回类型为泛型</typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static T Sum<T>(T a, T b)
        {
            //dynamic:它是在程序运行的时候才知道自己是什么类型
            return (dynamic)a + b;
        }
    }
}

3、泛型类中的泛型方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Generic
{
    //如果一个类下面有多个泛型方法,建议将这个类定义成泛型类,其中的方法就不需要再在方法名后面跟<T>了
    public class GenericType<T>
    {
        /// <summary>
        /// 泛型属性
        /// </summary>
        public T ItemName { get; set; }
        public string MyName { get; set; }   //也可定义其他属性
        public int id { get; set; }   //也可定义其他属性

        /// <summary>
        /// 泛型类中的泛型方法
        /// </summary>
        /// <typeparam name="T">返回类型为泛型</typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static T Sum(T a, T b)
        {
            //dynamic:它是在程序运行的时候才知道自己是什么类型
            return (dynamic)a + b;
        }
        public static void Print()
        {
            //运用反射获取类名
            Console.WriteLine($"{typeof(T).Name}");
        }
    }
    public class Student
    {
        public int StudentId { get; set; }
        public string StudentName { get; set; }
    }
}

4、运行程序

using Generic;

var s = MyClass.Sum(1, 2);
Console.WriteLine(s);

GenericType<Student>.Print();

泛型约束

​ 约束 说明
where T : struct 对于结构约束,类型T必须是值类型
where T : class 类约束指定类型T必须是引用类型
where T : IFoo 指定类型T必须实现接口IFoo
where T : Foo 指定类型T必须继承(派生)自基类Foo
where T : new() 指定类型T必须有一个默认构造函数(必须有无参构造);当使用多个约束时,这个约束必须放在最后
where T1 : T2 这个约束也可以指定,类型T1派生自泛型类型T2

注意:
只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束。
使用泛型类型还可以合并多个约束。where T:IFoo,new()约束和MyClass<T>声明指定,类型T必须实现IFoo接口,且必须有一个默认构造函数。但是不能既是值类型又是引用类型:where T : struct,class,这样就会报错
在C#中,where子句的一个重要限制是,不能定义必须由泛型类型实现的运算符。运算符不能再接口中定义。在where子句中,指定定义基类、接口和默认构造函数。

14.2 泛型集合

14.2.1 为什么使用泛型集合
using System.Collections;

ArrayList arraList = new ArrayList() { 14,"hello",29.7,true};
arraList.Add("world");  //object
double sum = 0;
foreach (var i in arraList) { 
    sum += Convert.ToDouble(i);  //出现类型转换异常
}
  1. 存取数据需要进行装箱拆箱
  2. 数据转换存在隐患
14.2.2 泛型Dictionary<K,V> 键值对 字典(键必须唯一)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 泛型集合
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //泛型字典
            Dictionary<int, string> dic = new Dictionary<int, string>();
            dic.Add(1,"小舒");
            dic.Add(2,"小明");
            dic.Add(3,"小红");
            foreach (var item in dic)
            {
                //Console.WriteLine(item);
                Console.WriteLine(item.Key+" "+item.Value);
            }
            var isContains = dic.ContainsValue("小舒");
            Console.WriteLine(isContains);
            Console.ReadLine();
        }
    }
}
14.2.3 List集合

使用场景:

  1. 在LINQ中比较常见
  2. 存储数据

声明

声明泛型集合
List<T> 集合名 = new List<T>();

例如:
//值类型
List<int> list = new List<int>();
//引用类型
List<Person> personList = new List<Person>();
  1. T只是占位符,会被传递的数据类型替换
  2. 实例化List时传入相应的数据类型
  3. 长度以2倍速度扩容

List常用属性

CountList集合中当前存储的元素个数
CapacityList集合当前容量 Capacity>=Count
List<int> list = new List<int>{2,3,5,4,8}   //集合初始化器
Count:5   Capacity:8

注意:
当我们使用List<T> list = new List<T>();实例化一个List对象是,.Net Framework只是在内存中申请了一块内存在存放list对象本身,系统此时并不知道list会有多少item元素。当我们向list添加第一个item时,list会申请能存储4个Item元素的存储空间,此时Capacity是4,但是当我们添加到第五个item时,此时的Capacity就会变成8,也就是当list发现元素的总数大于Capacity数量时,会主动申请并重新分配内存,当我们添加到第九个item时,Capacity不是12而是16,也就是说list每次申请的内存数量都是之前item元素数量两倍。然后将当前所有的item元素系但添加的元素复制到新的内存。

内存扩展原理

1、当list集合中元素为0时,容量为0

List<int> list = new List<int>() {};
//Console.WriteLine("元素的个数:{0},容量:{1}",list.Count,list.Capacity);
Console.WriteLine($"元素的个数:{list.Count},容量:{list.Capacity}");

输出:
元素的个数:0,容量:0

2、当list集合中元素为1时,list会申请到能存储4个Item元素的存储空间(系统默认分配)

List<int> list = new List<int>() { 1 };
Console.WriteLine($"元素的个数:{list.Count},容量:{list.Capacity}");

输出:
元素的个数:1,容量:4
List<int> list = new List<int>() { 1 , 2 , 3 , 4 };
Console.WriteLine($"元素的个数:{list.Count},容量:{list.Capacity}");

输出:
元素的个数:4,容量:4

3、当向list中添加到第五个元素时,此时的Capacity就会变成8,也就是当list发现元素的总数大于Capacity数量时,会主动申请并重新分配内存,也就是说list每次申请的内存数量都是之前item元素数量两倍

List<int> list = new List<int>() { 1 , 2 , 3 , 4 , 5};
Console.WriteLine($"元素的个数:{list.Count},容量:{list.Capacity}");

输出:
元素的个数:5,容量:8
List<int> list = new List<int>() { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
Console.WriteLine($"元素的个数:{list.Count},容量:{list.Capacity}");

输出:
元素的个数:9,容量:16

4、以此类推

List常用方法

new List<T>()。其中T为列表中元素类型
List<string> mList = new List<string>();

new List<T> (IEnumerable<T> collection)。以一个集合作为参数创建List.
string[] temArr = { "Ha", "Hunter", "Tom", "Lily", "Jay", "Jim", "Kuku", "Locu" };
List<string> testList = new List<string>(temArr);

Add( ) 在List中添加一个对象的公有方法
mList.Add("John");

AddRange( ) 公有方法,在List尾部添加实现了ICollection接口的多个元素
string[] temArr = { "Ha","Hunter", "Tom","Lily", "Jay","Jim", "Kuku",  "Locu"};
mList.AddRange(temArr);

BinarySearch( ) 重载的公有方法,用于在排序的List内使用二分查找来定位指定元素.

Clear( ) 在List内移除所有元素

Contains( ) 测试一个元素是否在List内
if(mList.Contains("Hunter"))
  {
  		Console.WriteLine("There is Hunter in the list");
  }
  else
  {
	  mList.Add("Hunter");
	  Console.WriteLine("Add Hunter successfully.");
  }

CopyTo( ) 重载的公有方法,把一个List拷贝到一维数组内

Exists方法。检测一个元素是否在List内。
具体方法定义为:public bool Exists(Predicate match);
其中方法形参为Predicate match,Predicate为一个委托,表示定义一组条件并确定指定对象是否符合这些条件的方法。
故而,Exists除了可以委托使用Lambda表达式,也可定义一个具体的方法作为委托。

//Lambda表达式
bool isExists = testList.Exists(t => t.Index == 7);
//委托方法
isExists = testList.Exists(JudgeExists);
public bool JudgeExists()
{
	if(....)//当t.Index == 7存在时
	{
		return true;
	}
	return false;
}

Find方法。查找并返回List内的出现的第一个匹配元素。
var element = testList.Find(t => t.Name == "wangwei");

FindAll( ) 查找并返回List内的所有匹配元素

GetEnumerator( ) 重载的公有方法,返回一个用于迭代List的枚举器

Getrange( ) 拷贝指定范围的元素到新的List内

IndexOf( ) 重载的公有方法,查找并返回每一个匹配元素的索引

Insert( ) 在List内插入一个元素

InsertRange( ) 在List内插入一组元素

LastIndexOf( ) 重载的公有方法,,查找并返回最后一个匹配元素的索引

Remove( ) 移除与指定元素匹配的第一个元素

RemoveAt( ) 移除指定索引的元素。移除后,后续索引元素会自动前移。

RemoveRange( ) 移除指定范围的元素

Reverse( ) 反转List内元素的顺序

Sort( ) 对List内的元素进行排序,按字母升序排序。

ToArray( ) 把List内的元素拷贝到一个新的数组内

trimToSize( ) 将容量设置为List中元素的实际数目
14.2.4 List集合和泛型Dictionary<K,V> 键值对字典的比较

参考文献:http://events.jianshu.io/p/6e701ee2f0b8

List集合

  • 优点
    1. List就是一个集合,它可以存储多种数据类型或者对象,也可以存储字典类型的列表Dictionary<K,V>,元素有放入顺序
    2. List是对数组做了一层包装,我们在数据结构上称之为线性表,而线性表的概念是,在内存中的连续区域,除了首节点和尾节点外,每个节点都有着其唯一的前驱结点和后续节点。由于它在内存区域的连续性,List只需要进行最少的内存换页即可,这就是List和Dictionary在遍历时效率差异的根本原因。
  • 缺点
    1. 元素可重复,需要自己去手写代码去筛选符合条件的元素放入List中
    2. 不能查找指定位置的元素,只能通过逐个遍历去查找或者用Equals方法区获取是否包含
    3. 在List集合中移除一个元素后,后续元素会往前移动,存在内存中移动后续元素的性能开销。

泛型Dictionary<K,V> 键值对字典

  • 优点

    1. Dictionary<K,V>,我们俗称其为字典,它包含一个Key和与之对应的Value,其目的是能够根据Key迅速地找到Value

    2. **Dictionary<K,V>**的存储原理

      关于字典的存储结构,它会根据Key通过Hash计算来得到其应存放的虚拟内存地址,这也是在哈希表中Key必须唯一的原因,当我们按照Key进行查找时,首先就是根据Key计算出其所存放的虚拟内存地址,去对应的内存地址找数据,得到其Value。

      这一点HashTable与其相同。

    3. 字典的主要特征是能够根据键快速查找值,也可以自由添加和删除元素,类似List<T>,但没有在内存中移动后续元素的性能开销。

    4. 编译时检查类型约束,无需拆箱装箱操作,与哈希表类似

      装箱:将值类型转换为引用类型
      拆箱:将引用类型转换为值类型
      条件:看两种类型是否发生了装箱和拆箱,要看,这两种类型是否存在继承关系。
      
  • 缺点

    1. 遍历问题:HashTable或者Dictionary,他是根据Key而根据Hash算法分析产生的内存地址,因此在宏观上是不连续的,由于这样的不连续,在遍历时,Dictionary必然会产生大量的内存换页操作,这样就导致它的效率没有List集合效率高
    2. Dictionary的存储空间问题:在Dictionary中,除了要存储我们实际需要的Value外,还需要一个辅助变量Key,这就造成了内存空间的双重浪费。
    3. 在尾部插入时,List只需要在其原有的地址基础上向后延续存储即可,而Dictionary却需要经过复杂的Hash计算,这也是性能损耗的地方。
    4. 不能在Dictionary<K,V>中指定位置添加元素

使用场景

什么时候用List集合?

  1. 当需要往集合中指定位置插入元素时,适合使用List集合;

  2. 当对运行效率、性能方面有要求时,使用List集合更好些

  3. 从性能上来说,如果存取的数组仅有一种数据类型,那么List<T>是最优选择。

什么时候用泛型Dictionary<K,V> 键值对字典?

  1. 当大量使用key来查找value的时候,Dictionary<K,V>无疑是最佳选择。

14.3 综合练习

using System.Collections;
//在使用的时候就规定是什么类型,在存取数据的时候,不需要再进行多余的装箱和拆箱操作
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

list.Insert(1, 10);

//var s = list.First();

//Console.WriteLine(s);
//foreach (var item in list)
//{
//    Console.Write(item + " ");
//}
//foreach (var item in list)
//{
//    if (item.Equals(2))
//    {
//        Console.WriteLine(item);
//    }
//}

//Console.WriteLine($"元素的个数:{list.Count},容量:{list.Capacity}");

//有序插入
Console.WriteLine("Dictionary:");
Dictionary<int, int> dic = new Dictionary<int, int>();
dic.Add(1, 5);
dic.Add(10, 3);
dic.Add(2, 5);
/*foreach (int key in dic.Keys)
{
    Console.Write(key+" ");
}*/
foreach (var item in dic)
{
    if (item.Key == 10)
    {
        Console.WriteLine(item.Value);
    }
}

//Console.WriteLine();

无序插入
//Console.WriteLine("Hashtable:");
//Hashtable hashtable = new Hashtable();
//hashtable.Add(1, 5);
//hashtable.Add(10, 3);
//hashtable.Add(2, 5);
//foreach (object key in hashtable.Keys)
//{
//    Console.Write(key.ToString() + " ");
//}

十五、其他

15.1 JSON

15.1.1 初识JSON

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

JSON建构于两种结构:

  • “名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
  • 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。

JSON具有以下这些形式:

对象是一个无序的“‘名称/值’对”集合。一个对象以 {左括号 开始, }右括号 结束。每个“名称”后跟一个 :冒号 ;“‘名称/值’ 对”之间使用 ,逗号 分隔。

JSON特点

  • JSON 是轻量级的文本数据交换格式
  • JSON 独立于语言
  • JSON 具有自我描述性,更易理解

JSON语法规则

  • 数据在键值对中
  • 数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组
[
  {
    "Name": "黑龙江",
    "Cities": ["哈尔滨","大庆"]
  },
  {
    "Name": "广东",
    "Cities": ["广州","深圳"]
  },
  {
    "Name": "台湾",
    "Cities": ["台北","高雄"]
  }
]

JSON官网:https://www.json.org/json-zh.html

15.1.2通过JsonConvert解析JSON文件:反序列化

反序列化(Deserialize):把Json字符串转为对象(实体)的一个过程叫做反序列化

示例1:Skill.json文件解析

[
  {
    "id": "1",
    "name": "xiaoshu",
    "skill": "书法"
  },
  {
    "id": "2",
    "name": "xiaoyu",
    "skill": "绘画"
  }
]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Json
{
    internal class Skill
    {
        //此类中的属性名要与json文件中保存的键名一致,类型也要一致,否则会报错
        public string id { get; set; }
        public string name { get; set; }
        public string skill { get; set; }
    }
}
using Newtonsoft.Json;
using System;
using System.Text;

namespace Json
{
    class Progarm
    {
        static void Main(string[] args)
        {
            //反序列化(Deserialize):将字符串转换成对象的一个过程叫做反序列化
            try
            {
                //反序列化:以数组的形式返回
                Skill[] SkillArray = JsonConvert.DeserializeObject<Skill[]>(File.ReadAllText(@"D:\C#学习\JSON\Json\Json\Skill.json"));
                foreach (var skill in SkillArray)
                {
                    Console.WriteLine(skill.id + "," + skill.name + "," + skill.skill);
                }
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            
            //反序列化:以集合的形式返回
            List<Skill> SkillList = JsonConvert.DeserializeObject<List<Skill>>(File.ReadAllText(@"D:\C#学习\JSON\Json\Json\Skill.json"));
            foreach (var skill in SkillList)
            {
                Console.WriteLine(skill.id + "," + skill.name + "," + skill.skill);
            }
            Console.ReadLine();
            
            //将数据写入到Skill.json文件中
            string path = @"D:\C#学习\JSON\Json\Json\Skill.json";
            //先将文件内容反序列化出来
            var skills = JsonConvert.DeserializeObject<List<Skill>>(File.ReadAllText(path));
            List<string> skillList = new List<string>();
            Skill skill1 = new Skill()
            {
                id = "3",
                name = "xiaoming",
                skill = "演戏"
            };

            Skill skill2 = new Skill()
            {
                id = "4",
                name = "xiaofei",
                skill = "唱歌"
            };
            skills.Add(skill1);
            skills.Add(skill2);
            //添加内容之后再序列化回去并写入文件
            string jsonTxt = JsonConvert.SerializeObject(skills, Formatting.Indented);
            //将得到的string类型的jsonTxt放入List集合中,以便使用WriteAllLines方法
            skillList.Add(jsonTxt);

            //写入文件WriteAllLines方法要集合类型的参数,所以先声明一个List<string> skillList = new List<string>();
            File.WriteAllLines(path, skillList);
            Console.WriteLine("写入成功");
        }
    }
}

【注意:Skill文件需要保存为UTF-8格式的文件,否则会出现中文乱码或者报如下错误】

Unexpected character encountered while parsing value: J. Path '', line 0, po

示例2:Json.json文件解析

[
  {
    "Name": "黑龙江",
    "Cities": [
      "哈尔滨",
      "大庆"
    ]
  },
  {
    "Name": "广东",
    "Cities": [
      "广州",
      "深圳"
    ]
  },
  {
    "Name": "台湾",
    "Cities": [
      "台北",
      "高雄"
    ]
  },
  {
    "Name": "新疆",
    "Cities": [
      "乌鲁木齐"
    ]
  }
]
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Json
{

    class Json
    {
        //此类中的属性名要与json文件中保存的键名一致,类型也要一致(数组类型也需要将属性类型写成数组类型,如下),否则会报错
        public string Name { get; set; }
        public string[] Cities { get; set; }
    }
   
}
using Newtonsoft.Json;
using System;
using System.Text;

namespace Json
{
    class Progarm
    {
        static void Main(string[] args)
        {
            //反序列化(Deserialize):将字符串转换成对象的一个过程叫做反序列化
            try
            {
                Json[] JsonlArray = JsonConvert.DeserializeObject<Json[]>(File.ReadAllText(@"D:\C#学习\JSON\Json\Json\Json.json"));
                foreach (var Json in JsonlArray)
                {
                    Console.Write(Json.Name+" ");
                    foreach (var City in Json.Cities)
                    {
                        Console.WriteLine("\t"+City);
                    }
                }
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

【注意:键值对中的值以数组类型存放则声明变量时需要以数组类型声明(遍历时用数组下标),否则会报如下错误】

Unexpected character encountered while parsing value: [. Path '[0].Cities', line 4, position 15.
15.1.3 对象的序列化

序列化(Serialize):将对象转换成字符串的一个过程叫做序列化

            //序列化(Serialize):把对象转换为JSON字符串的一个过程叫做序列化
            
            Skill skill = new Skill();
            skill.id = "100";
            skill.name = "xiaoming";
            skill.skill = "演戏";

            string str1 = JsonConvert.SerializeObject(skill, Formatting.Indented);
            Console.WriteLine(str1);

            string[] names = { "张三", "李四", "王五" };
            string str2 = JsonConvert.SerializeObject(names, Formatting.Indented);
            Console.WriteLine(str2);
15.1.4 使用JSON反序列化读取JSON对象
  1. Handwritten_021479.json
{
  "subject_id": "Handwritten",
  "contribution_date": 20220814,
  "device_info": {
    "additional": {
      "device_group": "b"
    }
  },
  "contributor_info": {
    "country": "PL"
  },
  "assets": [
    {
      "asset_basename": "402ecd90e664b2b812643505f001c8e3.MOV",
      "asset_md5": "402ecd90e664b2b812643505f001c8e3",
      "asset_metadata": {
        "content_info": {
          "lighting": "normal"
        },
        "additional": {
          "category": "receipt",
          "language": "UK",
          "store": {
            "name": "Alfa",
            "address": "Lisova"
          }
        }
      }
    },
    {
      "asset_basename": "0f941c5e3fdef16b026cfa6cd1201d07.HEIC",
      "asset_md5": "0f941c5e3fdef16b026cfa6cd1201d07",
      "asset_metadata": {
        "content_info": {
          "lighting": "normal"
        },
        "additional": {
          "category": "receipt",
          "language": "UK",
          "store": {
            "name": "Alfa",
            "address": "Lisova"
          }
        }
      }
    }
  ]
}

2、建立对应的类

public class Handwritten
    {
        public string subject_id { get; set; }
        public long contribution_date { get; set; }  //在JSON文件中是以对象的形式保存
        public DeviceInfo device_info { get; set; }  //在JSON文件中是以对象的形式保存
        public ContributorInfo contributor_info { get; set; }  //在JSON文件中是以对象的形式保存
        public List<Assets> assets { get; set; }  //在JSON文件中是以数组的形式保存

    }
public class DeviceInfo
    {
        public Additional1 additional { get; set; }
    }
public class Additional1  //device_info下的additional,两个additional不是同一个,所以需要建两个不同的类
    {
        public string device_group { get; set; }
    }
public class ContributorInfo
    {
        public string country { get; set; }
    }
public class Assets
    {
        public string asset_basename { get; set; }
        public string asset_md5 { get; set; }
        public AssetMetadata asset_metadata { get; set; }

    }
public class AssetMetadata
    {
        public ContentInfo content_info { get; set; }
        public Additional2 additional { get; set; }
    }
public class ContentInfo
    {
        public string lighting { get; set; }
    }
public class Additional2  //asset_metadata下的additional,两个additional不是同一个,所以需要建两个不同的类
    {
        public string category { get; set; }
        public string language { get; set; }
        public Store store { get; set; }
    }
public class Store
    {
        public string name { get; set; }
        public string address { get; set; }
    }

3、运行程序,读取对象内容

using JsonExer;
using Newtonsoft.Json;

try
{
    Handwritten handwrittens = JsonConvert.DeserializeObject<Handwritten>(File.ReadAllText(@"D:\C#学习\JSON\JsonExer\JsonExer\Handwritten_021479.json"));
    //Console.WriteLine(handwrittens.subject_id + "\n" + handwrittens.contribution_date+ "\n"+handwrittens.device_info.additional.device_group);
    
    foreach(var handwritten in handwrittens.assets)
    {
        Console.WriteLine(handwritten.asset_basename);
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

4、拓展:对象与JSON字符串之间的相互转换

4.1 建立转换类JsonNewtonsoft

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace JsonExer
{
     static class JsonNewtonsoft
    {
        /// <summary>
        /// 把对象转换为JSON字符串
        /// </summary>
        /// <param name="o">对象</param>
        /// <returns>JSON字符串</returns>
        public static string ToJSON(this object o)
        {
            if (o == null)
            {
                return null;
            }
            return JsonConvert.SerializeObject(o);
        }
        /// <summary>
        /// 把Json文本转为实体
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input"></param>
        /// <returns></returns>
        public static T FromJSON<T>(this string input)
        {
            try
            {
                return JsonConvert.DeserializeObject<T>(input);
            }
            catch (Exception ex)
            {
                return default(T);
            }
        }
        /// <summary>
        /// Regex.Replace(String, String, String)
        /// 查找替换文件内容
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <param name="searchText">查找内容</param>
        /// <param name="replaceText">替换为什么</param>
        public static void ReplaceFileContent(string filePath, string searchText, string replaceText)
        {
            StreamReader reader = new StreamReader(filePath);
            string content = reader.ReadToEnd();
            reader.Close();

            content = Regex.Replace(content, searchText, replaceText);

            StreamWriter writer = new StreamWriter(filePath);
            writer.Write(content);
            writer.Close();
        }
    }
}

4.2 将读取JSON对象转换为JSON字符串并输出到控制台

// See https://aka.ms/new-console-template for more information
//反序列化:以集合的形式返回
using JsonExer;
using Newtonsoft.Json;
using System.Runtime.Serialization.Json;
using System.Text;

try
{
    string path = @"D:\C#学习\JSON\JsonExer\JsonExer\Handwritten_021479.json";
    //var handwrittensTxt = JsonConvert.DeserializeObject<Handwritten>(File.ReadAllText(path));
    //var jsonTxt = JsonNewtonsoft.ToJSON(handwrittensTxt);
    //Console.WriteLine(jsonTxt);

    var jsonTxt = File.ReadAllText(path);
    var oldMd5Mov = "402ecd90e664b2b812643505f001c8e3";
    var newMd5Mov = "123";
    jsonTxt = jsonTxt.Replace(oldMd5Mov, newMd5Mov);
    File.WriteAllText(path, jsonTxt);
    Console.WriteLine("替换成功");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

15.2 XML

XML 指可扩展标记语言

语法规则:

  • 所有XML元素都必须有关闭标签

  • XML标签对大小写敏感,不能使用如下形式标签

    <Letter>这是错误的使用!</letter>
    
  • XML必须正确的嵌套

  • XML文档必须有根元素

  • XML的属性需加上引号

  • XML的注释

    <!-- 这是注释代码块 -->
    

15.5 反射简单了解

exe/dll文件:主要区别就是exe文件有入口(入口类:class Program 入口方法:static void Main(string[] args) {} )

metadata(元数据):描述exe/dll文件的一个数据清单

使用场景:

  1. 更新程序时(更新自己的dll)

  2. 使用别人的dll文件(这种可以读取别人私有的东西)

Reflection(反射):用来操作获取元数据(metadata)

总结:

反射就是一个操作metadata的一个类库(可以把反射当成用来读取或者操作元数据的一个小工具)

可以读取的包括但不限于:类、方法、属性、特性、字段等

使用场景:

asp.net MVC

ORM

AOP

几乎所有的框架都会使用反射

为什么要通过反射间接去操作?

因为 1-- 我们需要动态(写死的无法读取),2 – 读取私有的对象

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Reflection_Study
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                #region Reflection
                Console.WriteLine("-----------------------------Reflection---------------------------------");
                SQLServerHelper sqlServer = new SQLServerHelper();
                sqlServer.Query();
                //示例:
                //Assembly assembly = Assembly.Load("MySql.Data.dll");  //加载方式一:dll文件名,本类中的相对路径(可不需要后缀)
                //Assembly assembly = Assembly.LoadFile(@"D:\C#学习\C#基础\MySQLTest\MySQLTest\bin\Debug\MySql.Data.dll");  //加载方式二:dll文件的绝对路径(完整路径)
                Assembly assembly = Assembly.LoadFrom(@"D:\C#学习\C#基础\MySQLTest\MySQLTest\bin\Debug\MySql.Data.dll");  //加载方式二:完全限定名(带后缀)
                foreach (var type in assembly.GetTypes())
                {
                    Console.WriteLine(type.Name);
                    foreach (var method in type.GetMethods())
                    {
                        Console.WriteLine(method.Name);
                    }
                }

                #endregion

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

注意:以下的dll文件需要与运行项目在一个项目中(在其他项目中会找不到),会报如下错误

无法加载一个或多个请求的类型。有关更多信息,请检索 LoaderExceptions 属性。

15.6 特性

**特性(Attribute)**是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。

规定特性(Attribute)

规定特性(Attribute)的语法如下:

[attribute(positional_parameters, name_parameter = value, ...)]
element

特性(Attribute)的名称和值是在方括号内规定的,放置在它所应用的元素之前。positional_parameters 规定必需的信息,name_parameter 规定可选的信息。

本质:类,继承自Attribute类

使用场景

框架中、类上面、方法上面、属性上面、字段上面、参数上面

特性的使用:修改数据库表名

注:此处不需要自己去调用特性,因为框架已经帮你完成了

  1. 使用特性将web项目数据库中的表名movies修改为movie

    image-20221011115356164

  2. 在程序包管理器控制台输入

    add-migration addtablename
    
    update-database
    

    image-20221011115819616

特性的定义、查找、使用

  1. 特性的定义与配置

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyAttribute
    {
        /// <summary>
        /// 获取表名的特性
        /// </summary>
        /// 
        //AttributeUsage:特性的配置
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]  //Inherited = true:默认打开
        //特性的定义:
        internal class TableAttribute : Attribute
        {
            public string TableName { get; set; }
            public TableAttribute()
            {
    
            }
            public TableAttribute(string tableName)
            {
                this.TableName = tableName;
            }
        }
        internal class KeyAttribute : Attribute
        {
        }
    }
    
  2. Student类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyAttribute
    {
        [Serializable]
        //[Table]
        [Table("NewTable")]  // AllowMultiple = true 设置为true,默认false;否则会报错:特性重复
        internal class Student
        {
            [Key]
            public virtual int Id { get; set; }  //virtual:虚的属性可以派生
            public string Name { get; set; }
            public string Email { get; set; }
            public int Age { get; set; }
            public string PhoneNumber { get; set; }
        }
        class NewStudent : Student
        {
            [Key]
            public override int Id { get; set; }
        }
    }
    
  3. 查找特性:通过反射找到特性——》调用

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyAttribute
    {
        internal class CustomAttribute
        {
            /// <summary>
            /// 查找特性(获取表名)
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="mode"></param>
            /// <returns></returns>
            public static string GetTableNameAttribute<T>(T mode) where T : class
            {
                Type type = typeof(T);
                //需要判断一下需要获取的特性是否被定义
                if (type.IsDefined(typeof(TableAttribute),true))
                {
                    var attribute = type.GetCustomAttributes(typeof(TableAttribute),true);
                    return ((TableAttribute)attribute[0]).TableName;
                }
                else
                {
                    return type.Name;
                }
            }
        }
    }
    
  4. 测试

    // See https://aka.ms/new-console-template for more information
    using MyAttribute;
    
    Student student = new Student();
    string tableName = CustomAttribute.GetTableNameAttribute(student);
    Console.WriteLine(tableName);
    

常用特性

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyAttribute
{
    [Serializable]
    //[Table]
    [Table("NewTable")]  // AllowMultiple = true 设置为true,默认false;否则会报错:特性重复
    internal class Student
    {
        [Key] //说明这个属性是主键
        public int Id { get; set; }  

        [StringLength(maximumLength:50,MinimumLength =2)]  //字符串长度
        public string Name { get; set; }

        [EmailAddress]  //识别邮箱的格式
        public string Email { get; set; }

        [Required]//属性不能为空
        public int Age { get; set; }

        [Display(Name="电话号码")]  //显示字段的别名
        public string PhoneNumber { get; set; }
    }
}

扩展:常用特性编写及特性通用查找方法

/// <summary>
/// 抽象通用特性
/// </summary>
abstract class CommonValidateAttribute : Attribute{
    pubilc abstract bool IsValidate(object dataValue);
}
/// <summary>
/// 主键特性
/// </summary>
class KeyAttribute : CommonValidateAttribute
{
    public bool IsPrimaryKey { get; set; } = true;
    //特性验证方法
    public override bool Isvalidate(object dataValue)
    {
        return IsPrimaryKey;
    }
}
/// <summary>
/// 特性通用查找方法
/// </summary>
public static bool validate<T>(T model)
{
    //获取所有属性和特性
    PropertyInfo[] propertyInfos = model.GetType().GetProperties();//遍历属性和读取特性
    foreach (var property in propertyInfos)
    {   
        //判断是否有特性被定义
        if (property.IsDefined(typeof(CommonValidateAttribute), true))
        {
            //有,去拿到所有被定义的特性
            var attributes = property.GetcustomAttributes(typeof(commonValidateAttribute), true);
            foreach (var item in attributes)
            {   
                //将特性进行强制类型转换
                commonValidateAttribute attribute = item as CommonValidateAttribute;
                //进行特性验证:执行对应特性里面定义的验证方法
                if (!attribute.IsValidate(property.GetValue(model)))
                {
                    return false;
                }
            }
        }
    }
    return true;
}

十六、排序算法

数据结构和算法动态可视化:https://visualgo.net/zh/

16.1 冒泡排序

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BubbleSort
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //获得一组数字
            //string str = Console.ReadLine();
            //string[] strArray = str.Split();
            //int[] intArray = new int[strArray.Length];
            //for (int i = 0; i < strArray.Length; i++)
            //{
            //    int number = Convert.ToInt32(strArray[i]);
            //    intArray[i] = number;
            //}

            //输入一组数据
            int[] intArray = new int[] {19,50,19,16};
            for (int j = 0; j < intArray.Length - 1; j++)   //进行Length - 1轮比较
            {
                //进行比较 j=0 Length-1
                //j=1 Length - 1 - 1
                //j=2 Length - 1 - 2
                //需要一个变量标志,记录当前轮次比较是否有发生交换
                bool isChange = false;
                for(int i = 0; i < intArray.Length - 1 - j; i++)
                {
                    //如果左边大于右边,就交换
                    if (intArray[i]>intArray[i+1])
                    {
                        int temp = intArray[i];
                        intArray[i] = intArray[i+1];
                        intArray[i+1] = temp;
                        isChange = true;
                    }
                }
                if (isChange = false)
                {
                    break;
                }
            }
            foreach (int i in intArray)
            {
                Console.Write(i+" ");
            }
            Console.ReadLine();
        }
    }
}

十七、项目实战

17.1 交换文件内容中的两列并将其转换为Unix格式的文件

文件内容 文件格式属于Windows系统下文件格式

20	0101-2001-1300-1000-5072	1	30	01	01	Germany
20	0101-2001-1300-1000-5072	1	30	01	05	England
...(此处省略一万行)
20	0101-2001-1300-1000-5072	1	30	01	04	Chinese
20	0101-2001-1300-1000-5072	1	30	01	07	Germany

需求:交换 01 Germany 两列的位置,并且将文件格式转换为Unix系统下格式的文件

  1. 编写Trans类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SwapColumn
    {
        public static class Trans
        {
            public static readonly ConsoleColor defaultColor = Console.ForegroundColor;
            public static readonly Action<ConsoleColor, Action> ShowColorConsole = (color, action) =>
            {
                Console.ForegroundColor = color;
                action();
                Console.ForegroundColor = defaultColor;
            };
        }
    }
    
  2. 编写SwapColumnOperation类

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SwapColumn
    {
        public class SwapColumnOperation
        {
            /// <summary>
            /// 文件导入目录
            /// </summary>
            private static string ImportDir = "";
    
            /// <summary>
            /// 导出目录
            /// </summary>
            private static string ExportDir = "";
    
            /// <summary>
            /// 选择参数
            /// </summary>
            public static async Task ChooseParam()
            {
                Trans.ShowColorConsole(ConsoleColor.Yellow, () => Console.WriteLine("输入需要处理的文件目录:"));
                ImportDir = Console.ReadLine().TrimPath().ShouldDirectoryExist();
                Trans.ShowColorConsole(ConsoleColor.Yellow, () => Console.WriteLine("输入导出路径:"));
                ExportDir = Console.ReadLine().TrimPath().ShouldDirectoryExist();
                await Operation();
                
                Trans.ShowColorConsole(ConsoleColor.Green, () => Console.WriteLine("处理完成"));
                Trans.ShowColorConsole(ConsoleColor.Yellow, () =>
                {
                    Console.WriteLine("是不是要继续再运行一次?");
                    Console.WriteLine("\t输入1再运行一次");
                    Console.WriteLine("\t输入其它字符/回车直接退出.");
                });
                var lastLine = Console.ReadLine();
                if (lastLine?.Trim() == "1")
                {
                    await ChooseParam();
                }
            }
            /// <summary>
            /// 进行文件处理操作
            /// </summary>
            /// <returns>获取到输入目录下的所有文件</returns>
            public static async Task Operation()
            {
                var oldFiles = Directory.GetFiles(ImportDir, $"*.tsv", SearchOption.AllDirectories);
    
                foreach (var file in oldFiles)
                {
                    var allLines = new List<string>();
                    //按行读取所有文件中的内容
                    var txtLines = await File.ReadAllLinesAsync(file);
                    //遍历每一行
                    foreach (var line in txtLines)
                    {
                        //以tab键(\t:表示输入tab键之后产生的空格)对内容进行分割,得到一个分割后的数据数组
                        var sub = line.Split('\t');
    
                        //交换数组的第5、第6个元素
                        var temp = sub[5];
                        sub[5] = sub[6];
                        sub[6] = temp;
    
                        var newLine = string.Join("\t", sub);
                        allLines.Add(newLine);
                    }
                    string fileName = Path.GetFileName(file);
                    await File.WriteAllLinesAsync(Path.Combine(ExportDir, fileName), allLines);
                }
                //更改文件格式为Unix
                var unixFiles = Directory.GetFiles(ExportDir, $"*.tsv", SearchOption.AllDirectories);
                foreach (var unixFile in unixFiles)
                {
                    TextDocumentFileTypeConvert.Dos2Unix(unixFile);
                }
                OpenFolder(ExportDir);
            }
    
            /// <summary>
            /// 打开目录
            /// </summary>
            /// <param name="folderPath">目录路径</param>
            private static void OpenFolder(string folderPath)
            {
                if (string.IsNullOrEmpty(folderPath)) return;
    
                Process process = new Process();
                ProcessStartInfo psi = new ProcessStartInfo("Explorer.exe");
                psi.Arguments = folderPath;
                process.StartInfo = psi;
    
                try
                {
                    process.Start();
                }
                catch
                {
                    throw;
                }
                finally
                {
                    process?.Close();
    
                }
            }
            //判断输入路径下的目录是否存在
            public static string ShouldDirectoryExist(this string dirpath)
            {
                if (!Directory.Exists(dirpath))
                {
                    throw new DirectoryNotFoundException("目录" + dirpath + "不存在");
                }
    
                return dirpath;
            }
            //将路径的多余后缀去掉
            public static string TrimPath(this string path)
            {
                return path.TrimStart(Array.Empty<char>()).TrimEnd(' ', '\t', '\\', '/', '\t');
            }
        }
    }
    
  3. 编写TextDocumentFileTypeConvert类(支持多系统下的文件格式转换,需要的按需调用即可)

    using System;
    using System.IO;
    
    namespace SwapColumn
    {
        public class TextDocumentFileTypeConvert
        {
            public enum FileType
            {
                DOS,
                UNIX,
                MAC
            }
    
            private const byte CR = 0x0D;//
    
            private const byte LF = 0x0A;//
    
            private static readonly byte[] DOS_LINE_ENDING = new byte[] { CR, LF };
    
            public static FileType DetermineFileFormat(string fileName)
            {
                byte[] data = File.ReadAllBytes(fileName);
                int position = 0;
                if (Array.IndexOf(data, LF, position) >= 0)
                {
                    if (Array.IndexOf(data, CR, position) >= 0)
                    {
                        return FileType.DOS;
                    }
                    return FileType.UNIX;
    
                }
                if (Array.IndexOf(data, CR, position) >= 0)
                {
                    return FileType.MAC;
                }
                return FileType.DOS;
            }
    
            public static void AutoFileTypeConvert(string fileName, FileType fileType)
            {
                FileType oldFileType = DetermineFileFormat(fileName);
    
                if (fileType == FileType.DOS)
                {
                    if (oldFileType == FileType.DOS)
                        return;
                    else if (oldFileType == FileType.UNIX)
                        Unix2Dos(fileName);
                    else if (oldFileType == FileType.MAC)
                        Mac2Dos(fileName);
                }
                else if (fileType == FileType.UNIX)
                {
                    if (oldFileType == FileType.UNIX)
                        return;
                    else if (oldFileType == FileType.DOS)
                        Dos2Unix(fileName);
                    else if (oldFileType == FileType.MAC)
                        Mac2Unix(fileName);
                }
                else if (fileType == FileType.MAC)
                {
                    if (oldFileType == FileType.MAC)
                        return;
                    else if (oldFileType == FileType.DOS)
                        Dos2Mac(fileName);
                    else if (oldFileType == FileType.UNIX)
                        Unix2Mac(fileName);
                }
            }
    
            public static void Unix2Dos(string fileName)
            {
                byte[] data = File.ReadAllBytes(fileName);
                using (FileStream fileStream = File.OpenWrite(fileName))
                {
                    BinaryWriter bw = new BinaryWriter(fileStream);
                    int position = 0;
                    int index = 0;
                    do
                    {
                        index = Array.IndexOf(data, LF, position);
                        if (index >= 0)
                        {
                            if (index > 0 && data[index - 1] == CR)
                            {
                                bw.Write(data, position, index - position + 1);
                            }
                            else
                            {
                                bw.Write(data, position, index - position);
                                bw.Write(DOS_LINE_ENDING);
                            }
                            position = index + 1;
                        }
                    }
                    while (index >= 0);
                    bw.Write(data, position, data.Length - position);
                    fileStream.SetLength(fileStream.Position);
                }
            }
    
            public static void Mac2Dos(string fileName)
            {
                byte[] data = File.ReadAllBytes(fileName);
                int len = data.Length - 1;
                using (FileStream fileStream = File.OpenWrite(fileName))
                {
                    BinaryWriter bw = new BinaryWriter(fileStream);
                    int position = 0;
                    int index = 0;
                    do
                    {
                        index = Array.IndexOf(data, CR, position);
                        if (index >= 0)
                        {
                            if (index >= 0 && index < len && data[index + 1] == LF)
                            {
                                bw.Write(data, position, index - position + 1);
                            }
                            else
                            {
                                bw.Write(data, position, index - position);
                                bw.Write(DOS_LINE_ENDING);
                            }
                            position = index + 1;
                        }
                    } while (index >= 0);
                    bw.Write(data, position, data.Length - position);
                    fileStream.SetLength(fileStream.Position);
                }
            }
    
            public static void Dos2Unix(string fileName)
            {
                byte[] data = File.ReadAllBytes(fileName);
                using (FileStream fileStream = File.OpenWrite(fileName))
                {
                    BinaryWriter bw = new BinaryWriter(fileStream);
                    int position = 0;
                    int index = 0;
                    do
                    {
                        index = Array.IndexOf(data, CR, position);
                        if (index >= 0)
                        {
                            if (index > 0 && data[index + 1] == LF)
                            {
                                bw.Write(data, position, index - position);
                            }
                            else
                            {
                                bw.Write(data, position, index - position + 1);
                            }
                            position = index + 1;
                        }
                    }
                    while (index >= 0);
                    bw.Write(data, position, data.Length - position);
                    fileStream.SetLength(fileStream.Position);
                }
            }
    
            public static void Dos2Mac(string fileName)
            {
                byte[] data = File.ReadAllBytes(fileName);
                using (FileStream fileStream = File.OpenWrite(fileName))
                {
                    BinaryWriter bw = new BinaryWriter(fileStream);
                    int position = 0;
                    int index = 0;
                    do
                    {
                        index = Array.IndexOf(data, LF, position);
                        if (index >= 0)
                        {
                            if (index > 0 && data[index - 1] == CR)
                            {
                                bw.Write(data, position, index - position);
                            }
                            else
                            {
                                bw.Write(data, position, index - position + 1);
                            }
                            position = index + 1;
                        }
                    }
                    while (index >= 0);
                    bw.Write(data, position, data.Length - position);
                    fileStream.SetLength(fileStream.Position);
                }
            }
    
            public static void Mac2Unix(string fileName)
            {
                byte[] data = File.ReadAllBytes(fileName);
                int len = data.Length - 1;
                using (FileStream fileStream = File.OpenWrite(fileName))
                {
                    BinaryWriter bw = new BinaryWriter(fileStream);
                    int position = 0;
                    int index = 0;
                    do
                    {
                        index = Array.IndexOf(data, CR, position);
                        if (index >= 0)
                        {
                            if (index >= 0 && index < len && data[index + 1] == LF)
                            {
                                bw.Write(data, position, index - position + 1);
                            }
                            else
                            {
                                bw.Write(data, position, index - position);
                                bw.Write(LF);
                            }
                            position = index + 1;
                        }
                    } while (index >= 0);
                    bw.Write(data, position, data.Length - position);
                    fileStream.SetLength(fileStream.Position);
                }
            }
    
            public static void Unix2Mac(string fileName)
            {
                byte[] data = File.ReadAllBytes(fileName);
                using (FileStream fileStream = File.OpenWrite(fileName))
                {
                    BinaryWriter bw = new BinaryWriter(fileStream);
                    int position = 0;
                    int index = 0;
                    do
                    {
                        index = Array.IndexOf(data, LF, position);
                        if (index >= 0)
                        {
                            if (index > 0 && data[index - 1] == CR)
                            {
                                bw.Write(data, position, index - position + 1);
                            }
                            else
                            {
                                bw.Write(data, position, index - position);
                                bw.Write(CR);
                            }
                            position = index + 1;
                        }
                    }
                    while (index >= 0);
                    bw.Write(data, position, data.Length - position);
                    fileStream.SetLength(fileStream.Position);
                }
            }
        }
    }
    
  4. 编写Program类并运行程序

    // See https://aka.ms/new-console-template for more information
    using SwapColumn;
    
    await SwapColumnOperation.ChooseParam();
    //await SwapColumnOperation.Operation();
    

17.2 项目发布流程

  1. 点击发布

    image-20220929183213538

  2. 编辑发布信息

    image-20220929183711259

  3. 点击文本发布选项,根据需求选择后保存即可

    image-20220929183826699

  4. 最后点击发布就生成了可执行的.exe文件

    image-20220929184042206

  5. 打开文件夹

    image-20220929184148516

    image-20220929184221197

  6. 执行.exe文件即可

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值