C#新手学习杂记

178 阅读7分钟

多线程学习杂记

1,使用new Task或者Task.Factory.StartNew创建父线程与子线程时,如果没有在子线程创建的时候添加“TaskCreationOptions.AttachedToParent”参数,则两个线程没有依附关系。 只有子线程添加了“TaskCreationOptions.AttachedToParent”参数后,在父线程等待的时候,才会将子线程视做父线程的一部分,等子线程运行完毕再执行父线程中的代码。

Task task_Parent = Task.Factory.StartNew(()=>
{
    Task task_Child = Task.Factory.StartNew(()=>
    {
        Thread.Sleep(5000);
        Console.WriteLine("子线程执行完毕...")
    })
    Console.WriteLine("父线程执行完毕")
})
task_Parent.ContinueWith(t=>{Console.WriteLine("11111")})

//本质上还是两个彼此独立的线程,执行结果是
"父线程执行完毕"
"11111"
5秒后——"子线程执行完毕..."
Task task_Parent = Task.Factory.StartNew(()=>
{
    //加上TaskCreationOptions.AttachedToParent参数
    Task task_Child = Task.Factory.StartNew(()=>
    {
        Thread.Sleep(5000);
        Console.WriteLine("子线程执行完毕...")
    },TaskCreationOptions.AttachedToParent)
    Console.WriteLine("父线程执行完毕")
})
task_Parent.ContinueWith(t=>{Console.WriteLine("11111")})
//"父线程执行完毕"
//5秒后——"子线程执行完毕..."
//"11111"
//父线程依旧会正常执行,但是在ContinueWith的时候,会把子线程视做父线程的一部分,等父线程中的子线程全部执行完,才会执行ContinueWith中的代码。

(如果父线程是需要拿到子线程中的返回值,则需要先等待子线程执行完才应该继续执行父线程中的代码。)

Task.Run无法添加“TaskCreationOptions”参数,因为其底层代码中含有“TaskCreationOptions.DenyChildAttach”。所以Task.Run创建的子线程无法与父线程形成依赖关系。

SQLServer学习杂记

创建一个自己的SQLHelper类,在类中使用command.ExecuteReader(CommandBehavior.CloseConnection)方法时,需要返回一个SQLDataReader类型,这个类型在类外(如Main方法中)调用中,因为需要读取器(reader)打开并遍历,读取器可能会在某个时刻被释放并自动关闭,但这并不保证立即发生,因此连接也不能立即关闭。所以要么在外部显式的使用reader.close()方法,要么就在using块中使用SQLHelper类里包含ExecuteReader的方法。(PS,因为是在外部打开并遍历reader读取器,所以在SQLHelper类内部也不能直接使用close()等方法)

SqlParameter类是数据库操作中的一部分,它可以用来向数据库传递参数化查询的参数,确保用户提供的信息正确的识别为参数而不是SQL代码。

为确保Sqlconnection与SQLCommand能正确释放,应该使用using语句块来声明这俩对象。

单例模式

单例模式有三种实现方式,不管哪一种,都需要将构造方法私有化,使其无法在外部new出实例。

饿汉模式:类内声明一个私有的、静态的、只读的字段,类型为该class,赋值为该类的new实例,再通过一个静态属性的get方法或者一个公开的静态方法返回这个实例。

class AAA  
{  
    private AAA() { }  
  
    private static readonly AAA _instance = new AAA();  
    public static AAA GetInstance  
    {  
        get  
        {  
            return _aaa;  
        }  
    }  
}

懒汉模式:类内先声明一个私有静态该类的字段,但是先不要赋值,在静态属性的get方法中,先判断字段是否为null,如果是null,再对字段new实例后,返回出去。

class BBB  
{  
    private BBB() { }  
  
    private static  BBB _instance = null;  
    public static BBB GetInstance  
    {  
        get  
        {  
            if (_instance == null)  
            {  
                _instance = new BBB();  
            }  
            return _instance;  
        }  
    }  
}

使用懒汉模式还需要考虑到线程安全的问题,因为当单例还没有被实例化时的状态被多个线程同时取到,进而多个线程都会创建出实例,所以要使用lazy语法糖加锁,修改后:

class BBB  
{  
    private BBB() { }  
  
    private static readonly Lazy<BBB> _instance = new Lazy<BBB>(() => new BBB());  
    public static BBB GetInstance  
    {  
        get  
        {  
            return _instance.Value;  
        }  
    }  
}

饿汉模式与懒汉模式的区别,在于饿汉模式是程序执行前就已经把实例创建好了,会占用部分内存,但不会有线程问题;而懒汉模式因为添加的Lazy线程锁避免了线程问题,同时又保证了类在首次访问GetInstance时被创建。

内部静态类:在类中声明一个私有静态类classinner,在这个类中再公开一个静态只读的、类型为单例类的字段并new出实例,最后使用公开的静态属性中get方法返回静态类中的字段。

class CCC  
{  
    private CCC() { }  
  
    private static class CCCInner  
    {  
        static CCCInner() { }  
        public static readonly CCC _instance = new CCC();  
    }  
    public static CCC GetInstance { get { return CCCInner._instance; } }  
}

内部静态类实现方式巧妙利用了类加载机制和静态构造函数的特性,既保证了线程安全,又实现了延迟初始化,是一种高效且简洁的单例模式实现方式。

读取外部excel表

两种读取excel表的方法,一种是使用Microsoft.ACE.OLEDB组件,通过OLE DB接口访问Excel文件,在用法上,与连接SQL数据库没有太大差别

    string excelPath = @"\path";
    string connString = $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source{excelFilePath};Extended Properties='Excel 12.0 Xml;HDR=YES;'"
    //使用using确保资源正确释放
    //创建OLE DB连接
    using(OleDbConnection oconn = new OleDbConnection(connString)){
        oconn.Open();
        //定义SQL执行语句
        string SQL = "SELECT * FROM [Sheet1$]";
        //创建command对象
        using(var cmd = new OleDbCommand(SQL,oconn)){
            //创建读取器并执行查询
            using(var adapter = new OleDbDataAdapter(cmd)){
                //将读取器中的结果,填充到dataTable中
                DataTable dt = new DataTable();
                adapter.Fill(dt);
            }
        }
    }

以上是处理单个独立的表的时候用到的方式,如果要处理多个相关联的表,则需要将读取器中的结果填充到DataSet中,DataSet 可以容纳多个 DataTable,并允许您通过 DataRelation 来定义和维护表间关系,便于进行数据的导航和关联查询。

另一种是如果没有Microsoft.ACE.OLEDB组件,可以通过nuget包里的 NPOI库来对外部的.xls或.xlsx表读取, NPOI库中有两个类可以用来创建和代表Excel的类(HSSFWorkbook,XSSFWorkbook)前者用于.xls,后者用于.xlsx。 用ISheet接口(继承了接口的HSSFSheet/XSSFSheet)来选择要读取的工作表。 之后可以通过ISheet中提供的方法遍历excel表中的行和列,将结果填充到DataTable中。(在Excel中,第一行为列标题,用来创建DataTable的列结构,之后遍历其余的行,填充到DataTable中)

using(var workBook = new XSSFWorkBook(excelFilePath)){
    //按数组下标选取读取的表
    var sheet = workBool.GetSheetAt(0);
    //声明新表来为填充做准备
    DataTable dt = new DataTable();
    //遍历所选表中的行
    foreach(IRow row in sheet){
        //通常第一行都是列标题
        if(row.RowNum == 0){
            for(i = 0,i<row.lastCellNum,i++){
                //这句的意思:先获取单元格中的对象,如果不是null则转成文本,如果是null则返回null,最后??表示如果左侧不是null则返回,否则将null转成Empty返回。
                string colName = row.GetCell(i)?.ToString()?? String.Empty;
                //表中添加列与列名
                dt.Columns.Add(colName);
            }
        }
        else{
            //第二行开始为需要的数据
            //创建好行数据,为填充DataTable做准备
            DataRow dr = new DataRow();
            //循环读取行中的数据并填充到dr中
            for(i = 0,i<row.lastCellNum,i++){
                Object value =  row.GetCell(i)?.ToString()?? String.Empty;
                dr[i] = value;
            }
            //往表中添加行数据
            dt.Rows.Add(dr);
        }
    }
}

OLEDB与NPOI库的优劣: 文言一心总结吧。

将DataTable导入数据库

有两种方式,

先假设数据库中有个StudentDB表,里面有学生的相关信息

class Student  
{  
    public int StudentId { get; set; }  
    public string StudentName { get; set; }  
    public DateTime StuBrithday { get; set; }  
    public int PhontNum { get; set; }  
}

以下为第一种方式

/// 通过SQL事务的方法把DataTable中的数据导入数据库  
static void ImportSQLServerDB(DataTable dt)  
{  
    //主是先将dt中的数据重新还原成一个个学生  
    var listStu = new List<Student>();  
    foreach (DataRow row in dt.Rows)  
    {  
        var stu = new Student()  
        {  
            StudentId = Convert.ToInt32(row["StudentId"]),  
            StudentName = row["StudentName"].ToString()!,  
            StuBrithday = Convert.ToDateTime(row["StuBrithday"]),  
            PhontNum = Convert.ToInt32(row["PhontNum"])  
        };  
        listStu.Add(stu);  
    }  
  
    var listSQL = new List<string>();  
    foreach (var stu in listStu)  
    {  
        string SQL = $"INNER INTO dbo.Students ([StudentId],[StudentName],[StuBrithday],[PhontNum]) VALUES ({stu.StudentId},'{stu.StudentName}','{stu.StuBrithday}',{stu.PhontNum})";  
        listSQL.Add(SQL);  
    }  
    //最后执行SQL事务...  
    SQLAddTransaction(listSQL);  
}

static void SQLAddTransaction(List<string> listSQL)  
{  
    string connString = "";  
    SqlConnection connection = new SqlConnection(connString);  
    SqlCommand cmd = connection.CreateCommand();  
    cmd.CommandType = CommandType.Text;  
  
    try  
    {  
        connection.Open();  
  
        cmd.Transaction = connection.BeginTransaction();  
        foreach (var sql in listSQL)  
        {  
            cmd.CommandText = sql;  
            cmd.ExecuteNonQuery();  
        }  
        cmd.Transaction.Commit();  
    }  
    catch (Exception ex)  
    {  
        cmd.Transaction?.Rollback();  
        throw new Exception(ex.Message);  
    }  
    finally  
    {  
        if (cmd.Transaction != null)  
        {  
            cmd.Transaction = null;  
        }  
        connection.Close();  
    }  
}

以下是第二种方式:

/// 通过SQLBulkCopy类将DataTable导入数据库  
static void ImportBulkCopy(DataTable sourceDt)  
{  
    //首先需要确保DataTable中的数据类型与数据库中一致  
    //新建一个DataTable  
    DataTable copyDt = sourceDt.Clone();  
    //设定好指定列的数据类型  
    copyDt.Columns["StudentId"].DataType = typeof(int);  
    copyDt.Columns["PhontNum"].DataType = typeof(int);  
    copyDt.Columns["StuBrithday"].DataType = typeof(DateTime);  
    //将原DataTable中的数据转换后,复制到新建的表中  
    foreach (DataRow row in sourceDt.Rows)  
    {  
        DataRow newRow = copyDt.NewRow();  
        if (int.TryParse(row["StudentId"].ToString(), out int intStuId))  
        {  
            newRow["StudentId"] = intStuId;  
        }  
  
        if (int.TryParse(row["PhontNum"].ToString(), out int intPhone))  
        {  
            newRow["PhontNum"] = intPhone;  
        }  
  
        if (int.TryParse(row["StuBrithday"].ToString(), out int intBrithday))  
        {  
            newRow["StuBrithday"] = intBrithday;  
        }  
  
        foreach (DataColumn col in sourceDt.Columns)  
        {  
            if (col.ColumnName != "StudentId" && col.ColumnName != "PhontNum" && col.ColumnName != "StuBrithday")  
            {  
                newRow[col.ColumnName] = row[col.ColumnName];  
            }  
        }  
  
        //通过SQLBulkCopy导入  
        BulkCopy(copyDt);  
    }  
}

static void BulkCopy(DataTable dt)  
{  
    //将与数据库中数据类型一致的DataTable通过BulkCopy类导入数据库  
    string connString = "";  
    SqlConnection connection = new SqlConnection(connString);  
    connection.Open();  
    SqlBulkCopy sqlBulk = new SqlBulkCopy(connection);  
  
    //指定要导入的表名  
    sqlBulk.DestinationTableName = "StudentDB";  
  
    //读取到的excel表中的列名可能使用汉字  
    //如有需要,要将DataTable中列名与数据库中的列名做好映射  
    sqlBulk.ColumnMappings.Add("学生ID", "StudentId");  
    sqlBulk.ColumnMappings.Add("姓名", "StudentName");  
    sqlBulk.ColumnMappings.Add("生日", "StuBrithday");  
    sqlBulk.ColumnMappings.Add("电话", "PhontNum");  
  
    sqlBulk.WriteToServer(dt);  
    connection.Close();  
}