1. 程式人生 > >C#內建泛型委託:Func委託

C#內建泛型委託:Func委託

1、什麼是Func委託

Func委託代表有返回型別的委託

2、Func委託定義

檢視Func的定義:

using System.Runtime.CompilerServices;

namespace System
{
    //
    // 摘要:
    //     封裝一個方法,該方法具有兩個引數,並返回由 TResult 引數指定的型別的值。
    //
    // 引數:
    //   arg1:
    //     此委託封裝的方法的第一個引數。
    //
    //   arg2:
    //     此委託封裝的方法的第二個引數。
    //
    // 型別引數:
    //   T1:
    
// 此委託封裝的方法的第一個引數的型別。 // // T2: // 此委託封裝的方法的第二個引數的型別。 // // TResult: // 此委託封裝的方法的返回值型別。 // // 返回結果: // 此委託封裝的方法的返回值。 [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")] public delegate TResult Func<in T1, in
T2, out TResult>(T1 arg1, T2 arg2); }

你會發現,Func其實就是有多個輸出引數並且有返回值的delegate。

3、示例

Func至少0個輸入引數,至多16個輸入引數,根據返回值泛型返回。必須有返回值,不可void。

Func<int> 表示沒有輸入參參,返回值為int型別的委託。

Func<object,string,int> 表示傳入引數為object, string ,返回值為int型別的委託。

Func<object,string,int> 表示傳入引數為object, string, 返回值為int型別的委託。

Func<T1,T2,,T3,int> 表示傳入引數為T1,T2,,T3(泛型),返回值為int型別的委託。

程式碼示例如下:

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

namespace FunDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 無引數,只要返回值 
            Func<int> fun1 = new Func<int>(FunWithNoPara);
            int result1= fun1();
            Console.WriteLine(result1);
            Console.WriteLine("----------------------------");
            Func<int> fun2 = delegate { return 19; };
            int result2 = fun2();
            Console.WriteLine(result2);
            Console.WriteLine("----------------------------");
            Func<int> fun3 = () => { return 3; };
            int result3 = fun3();
            Console.WriteLine(result3);
            Console.WriteLine("----------------------------");
            //有一個引數,一個返回值
            Func<int, int> fun4 = new Func<int, int>(FunWithPara);
            int result4 = fun4(4);
            Console.WriteLine($"這裡是一個引數一個返回值的方法,返回值是:{result4}");
            Console.WriteLine("----------------------------");
            // 使用委託
            Func<int, string> fun5 = delegate (int i) { return i.ToString(); };
            string result5 = fun5(5);
            Console.WriteLine($"這裡是一個引數一個返回值的委託,返回值是:{result5}");
            Console.WriteLine("----------------------------");
            // 使用匿名委託
            Func<int, string> fun6 = (int i) => 
            {
                return i.ToString();
            };
            string result6 = fun6(6);
            Console.WriteLine($"這裡是一個引數一個返回值的匿名委託,返回值是:{result6}");
            Console.WriteLine("----------------------------");
            // 多個輸入引數
            Func<int, string, bool> fun7 = new Func<int, string, bool>(FunWithMultiPara);
            bool result7 = fun7(2, "2");
            Console.WriteLine($"這裡是有多個輸入引數的方法,返回值是:{result7}");
            Console.WriteLine("----------------------------");
            // 使用委託
            Func<int, string, bool> fun8 = delegate (int i, string s) 
            {
                return i.ToString().Equals(s) ? true : false;
            };
            bool result8 = fun8(2, "abc");
            Console.WriteLine($"這裡是有多個輸入引數的委託,返回值是:{result8}");
            Console.WriteLine("----------------------------");
            // 使用匿名委託
            Func<int, string, bool> fun9 = (int i, string s) => 
            {
                return i.ToString().Equals(s) ? true : false;
            };
            bool result9 = fun9(45, "ert");
            Console.WriteLine($"這裡是有多個輸入引數的匿名委託,返回值是:{result9}");
            Console.ReadKey();

        }

        static int FunWithNoPara()
        {
            return 10;
        }

        static int FunWithPara(int i)
        {
            return i;
        }

        static bool FunWithMultiPara(int i,string s)
        {
            return i.ToString().Equals(s) ? true : false;
        }
    }
}

 執行結果:

4、真實示例

在下面的示例中,利用Func委託封裝資料庫通用訪問類。

1、定義BaseModel基類

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

namespace FunApplication.Model
{
    public class BaseModel
    {
        public int Id { get; set; }
    }
}

2、定義Student類繼承自BaseModel基類

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

namespace FunApplication.Model
{
    public class Student : BaseModel
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public int Sex { get; set; }

        public string Email { get; set; }
    }
}

3、定義資料庫訪問方法介面

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

namespace FunApplication.IDAL
{
    public interface IBaseDAL
    {
        T Query<T>(int id) where T : BaseModel;

        List<T> QueryAll<T>() where T : BaseModel;

        int Insert<T>(T t) where T : BaseModel;

        int Update<T>(T t) where T : BaseModel;

        int Delete<T>(int id) where T : BaseModel;
    }
}

4、定義屬性幫助類

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

namespace FunApplication.AttributeExtend
{
    public static class AttributeHelper
    {
        public static string GetColumnName(this PropertyInfo prop)
        {
            if (prop.IsDefined(typeof(ColumnAttribute), true))
            {
                ColumnAttribute attribute = (ColumnAttribute)prop.GetCustomAttribute(typeof(ColumnAttribute), true);
                return attribute.GetColumnName();
            }
            else
            {
                return prop.Name;
            }
        }
    }
}

5、定義ColumnAttribute類

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

namespace FunApplication.AttributeExtend
{
    [AttributeUsage(AttributeTargets.Property)]
    public class ColumnAttribute : Attribute
    {
        public ColumnAttribute(string name)
        {
            this._Name = name;
        }

        private string _Name = null;
        public string GetColumnName()
        {
            return this._Name;
        }
    }
}

6、定義資料庫方法介面實現類

using FunApplication.IDAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using FunApplication.AttributeExtend;

namespace FunApplication.DAL
{
    public  class BaseDAL : IBaseDAL
    {
        // 資料庫連結字串
        private static string strConn = ConfigurationManager.ConnectionStrings["DbConnection"].ConnectionString;
        public  int Delete<T>(int id) where T : BaseModel
        {
            int result = 0;

            using (SqlConnection conn = new SqlConnection(strConn))
            {
                string strSQL = "delete from Student where [email protected]";
                SqlParameter para = new SqlParameter("Id", id);
                SqlCommand command = new SqlCommand(strSQL, conn);
                command.Parameters.Add(para);
                conn.Open();
                result = command.ExecuteNonQuery();
            }
            return result;
        }

        public int Insert<T>(T t) where T : BaseModel
        {
            int result = 0;
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                Type type = typeof(T);
                var propArray = type.GetProperties().Where(p => p.Name != "Id");
                string strSQL = "insert into Student Values (@Name,@Age,@Sex,@Email) ";
                SqlCommand command = new SqlCommand(strSQL, conn);
                var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();
                command.Parameters.AddRange(parameters);
                conn.Open();
                result = command.ExecuteNonQuery();
            }
                return result;
        }

        public T Query<T>(int id) where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] WHERE Id={id}";
            T t = null;// (T)Activator.CreateInstance(type);

            using (SqlConnection conn = new SqlConnection(strConn))
            {
                SqlCommand command = new SqlCommand(sql, conn);
                conn.Open();
                SqlDataReader reader = command.ExecuteReader();
                List<T> list = this.ReaderToList<T>(reader);
                t = list.FirstOrDefault();          
            }
            return t;
        }

        public List<T> QueryAll<T>() where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] ";
            List<T> list = new List<T>();
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                SqlCommand command = new SqlCommand(sql, conn);
                conn.Open();
                SqlDataReader reader = command.ExecuteReader();
                list = this.ReaderToList<T>(reader);
            }
            return list;
        }

        public int Update<T>(T t) where T : BaseModel
        {
            int result = 0;
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                Type type = typeof(T);
                var propArray = type.GetProperties().Where(p => p.Name != "Id");
                string columnString = string.Join(",", propArray.Select(p => $"[{p.GetColumnName()}][email protected]{p.GetColumnName()}"));
                var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();                
                //必須引數化  否則引號?  或者值裡面還有引號
                string strSQL = $"UPDATE [{type.Name}] SET {columnString} WHERE Id={t.Id}";
                SqlCommand command = new SqlCommand(strSQL, conn);
                command.Parameters.AddRange(parameters);
                conn.Open();
                result = command.ExecuteNonQuery();            
            }
            return result;
        }

        private List<T> ReaderToList<T>(SqlDataReader reader) where T : BaseModel
        {
            Type type = typeof(T);
            List<T> list = new List<T>();
            while (reader.Read())//表示有資料  開始讀
            {
                T t = (T)Activator.CreateInstance(type);
                foreach (var prop in type.GetProperties())
                {
                    object oValue = reader[prop.GetColumnName()];
                    if (oValue is DBNull)
                        oValue = null;
                    prop.SetValue(t, oValue);//除了guid和列舉
                }
                list.Add(t);
            }
            return list;
        }
    }
}

7、在Main()方法中呼叫

using FunApplication.DAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            #region MyRegion
            BaseDAL dal = new BaseDAL();
            // 查詢
            Student student = dal.Query<Student>(2);
            Console.WriteLine($"姓名:{student.Name},年齡:{student.Age},Email地址:{student.Email}");
            Console.WriteLine("----------------------------");
            // 查詢所有
            List<Student> list = dal.QueryAll<Student>();
            Console.WriteLine($"集合個數:{list.Count}");
            Console.WriteLine("----------------------------");
            // 插入
            Student studentIns = new Student()
            {
                Name = "小明",
                Age = 20,
                Sex = 2,
                Email = "[email protected]"
            };
            bool resultIns = dal.Insert<Student>(studentIns) > 0 ? true : false;
            Console.WriteLine($"插入執行結果:{resultIns}");
            Console.WriteLine("----------------------------");
            // 更新
            Student studentUpd = new Student()
            {
                Id = 1,
                Name = "zhangsan1234",
                Age = 20,
                Sex = 2,
                Email = "[email protected]"
            };
            bool resultUpd = dal.Update<Student>(studentUpd) > 0 ? true : false;
            Console.WriteLine($"更新執行結果:{resultUpd}");
            Console.WriteLine("----------------------------");
            // 刪除
            bool resultDel = dal.Delete<Student>(3) > 0 ? true : false;
            Console.WriteLine($"刪除執行結果:{resultDel}");
            #endregion
            Console.ReadKey();
        }
    }
}

8、結果

 

9、優化

仔細觀察上面步驟7中的程式碼,你會發現在每個方法中都有重複的程式碼,開啟連結,執行SqlCommand命令,那麼這些重複的程式碼能不能提取到一個公共的方法中進行呼叫呢?答案是可以的,那就是利用Func委託,看下面優化後的程式碼:

using FunApplication.AttributeExtend;
using FunApplication.IDAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication.DAL
{
    public class FunBaseDAL : IBaseDAL
    {
        // 資料庫連結字串
        private static string strConn = ConfigurationManager.ConnectionStrings["DbConnection"].ConnectionString;

        public int Delete<T>(int id) where T : BaseModel
        {
            Type type = typeof(T);
            string sql = $"delete from {type.Name} where [email protected]";
            Func<SqlCommand, int> func = (SqlCommand command) => 
            {               
                SqlParameter para = new SqlParameter("Id", id);
                command.Parameters.Add(para);
                return command.ExecuteNonQuery();
            };

            return ExcuteSql<int>(sql, func);
        }

        public int Insert<T>(T t) where T : BaseModel
        {
            int result = 0;
            Type type = typeof(T);
            var propArray = type.GetProperties().Where(p => p.Name != "Id");
            string strSQL = "insert into Student Values (@Name,@Age,@Sex,@Email) ";
            var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();
            Func<SqlCommand, int> func = (SqlCommand command) => 
            {
                command.Parameters.AddRange(parameters);
                return command.ExecuteNonQuery();
            };
            result = ExcuteSql<int>(strSQL, func);
            return result;
        }

        public T Query<T>(int id) where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] WHERE [email protected]";
            T t = null;
            DataTable dt = new DataTable();
            
            Func<SqlCommand, T> func = (SqlCommand command) => 
            {
                SqlParameter para = new SqlParameter("@Id", id);
                command.Parameters.Add(para);
                SqlDataAdapter adapter = new SqlDataAdapter(command);
                //SqlDataReader reader = command.ExecuteReader();
                //List<T> list = this.ReaderToList<T>(reader);
                adapter.Fill(dt);
                List<T> list = ConvertToList<T>(dt);
                T tResult = list.FirstOrDefault();
                return tResult;
            };
            t = ExcuteSql<T>(sql, func);
            return t;
        }

        public List<T> QueryAll<T>() where T : BaseModel
        {
            Type type = typeof(T);
            string columnString = string.Join(",", type.GetProperties().Select(p => $"[{p.GetColumnName()}]"));
            string sql = $"SELECT {columnString} FROM [{type.Name}] ";
            T t = null;

            Func<SqlCommand, List<T>> func = (SqlCommand command) =>
            {
                SqlDataReader reader = command.ExecuteReader();
                List<T> list = this.ReaderToList<T>(reader);
                return list;
            };
            return ExcuteSql<List<T>>(sql, func);
        }

        public int Update<T>(T t) where T : BaseModel
        {
            int result = 0;
            Type type = typeof(T);
            var propArray = type.GetProperties().Where(p => p.Name != "Id");
            string columnString = string.Join(",", propArray.Select(p => $"[{p.GetColumnName()}][email protected]{p.GetColumnName()}"));
            var parameters = propArray.Select(p => new SqlParameter($"@{p.GetColumnName()}", p.GetValue(t) ?? DBNull.Value)).ToArray();
            //必須引數化  否則引號?  或者值裡面還有引號
            string strSQL = $"UPDATE [{type.Name}] SET {columnString} WHERE Id={t.Id}";
            Func<SqlCommand, int> func = (SqlCommand command) => 
            {
                command.Parameters.AddRange(parameters);
                return command.ExecuteNonQuery();
            };
            result = ExcuteSql<int>(strSQL, func);
            return result;
        }


        //多個方法裡面重複對資料庫的訪問  想通過委託解耦,去掉重複程式碼
        private T ExcuteSql<T>(string sql, Func<SqlCommand, T> func)
        {
            using (SqlConnection conn = new SqlConnection(strConn))
            {
                using (SqlCommand command = new SqlCommand(sql, conn))
                {
                    conn.Open();
                    SqlTransaction sqlTransaction = conn.BeginTransaction();
                    try
                    {
                        command.Transaction = sqlTransaction;
                        T tResult = func.Invoke(command);
                        sqlTransaction.Commit();
                        return tResult;
                    }
                    catch (Exception ex)
                    {
                        sqlTransaction.Rollback();
                        throw;
                    }
                }
            }
        }

        private List<T> ReaderToList<T>(SqlDataReader reader) where T : BaseModel
        {
            Type type = typeof(T);
            List<T> list = new List<T>();
            while (reader.Read())//表示有資料  開始讀
            {
                T t = (T)Activator.CreateInstance(type);
                foreach (var prop in type.GetProperties())
                {
                    object oValue = reader[prop.GetColumnName()];
                    if (oValue is DBNull)
                        oValue = null;
                    prop.SetValue(t, oValue);//除了guid和列舉
                }
                list.Add(t);
            }
            reader.Close();
            return list;
        }
    }
}

10、在Main()方法中呼叫

using FunApplication.DAL;
using FunApplication.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 傳統實現
            //BaseDAL dal = new BaseDAL();
            //// 查詢
            //Student student = dal.Query<Student>(2);
            //Console.WriteLine($"姓名:{student.Name},年齡:{student.Age},Email地址:{student.Email}");
            //Console.WriteLine("----------------------------");
            //// 查詢所有
            //List<Student> list = dal.QueryAll<Student>();
            //Console.WriteLine($"集合個數:{list.Count}");
            //Console.WriteLine("----------------------------");
            //// 插入
            //Student studentIns = new Student()
            //{
            //    Name = "小明",
            //    Age = 20,
            //    Sex = 2,
            //    Email = "[email protected]"
            //};
            //bool resultIns = dal.Insert<Student>(studentIns) > 0 ? true : false;
            //Console.WriteLine($"插入執行結果:{resultIns}");
            //Console.WriteLine("----------------------------");
            //// 更新
            //Student studentUpd = new Student()
            //{
            //    Id = 1,
            //    Name = "zhangsan1234",
            //    Age = 20,
            //    Sex = 2,
            //    Email = "[email protected]"
            //};
            //bool resultUpd = dal.Update<Student>(studentUpd) > 1 ? true : false;
            //Console.WriteLine($"更新執行結果:{resultUpd}");
            //Console.WriteLine("----------------------------");
            //// 刪除
            //bool resultDel = dal.Delete<Student>(5) > 1 ? true : false;
            //Console.WriteLine($"刪除執行結果:{resultDel}");
            #endregion

            #region 利用委託
            // 查詢
            FunBaseDAL dal = new FunBaseDAL();
            Student student = dal.Query<Student>(1);
            Console.WriteLine($"姓名:{student.Name},年齡:{student.Age},Email地址:{student.Email}");
            Console.WriteLine("----------------------------");
            // 查詢所有
            List<Student> list = dal.QueryAll<Student>();
            Console.WriteLine($"集合個數:{list.Count}");
            Console.WriteLine("----------------------------");
            // 插入
            Student studentIns = new Student()
            {
                Name = "tom",
                Age = 19,
                Sex = 1,
                Email = "[email protected]"
            };
            bool resultIns = dal.Insert<Student>(studentIns) > 0 ? true : false;
            Console.WriteLine($"插入執行結果:{resultIns}");
            Console.WriteLine("----------------------------");
            List<Student> list1 = dal.QueryAll<Student>();
            Console.WriteLine($"插入後集合個數:{list1.Count}");
            Console.WriteLine("----------------------------");
            // 更新
            Student studentUpd = new Student()
            {
                Id = 2,
                Name = "馬六123",
                Age = 20,
                Sex = 2,
                Email = "[email protected]"
            };
            bool resultUpd = dal.Update<Student>(studentUpd) > 0 ? true : false;
            Console.WriteLine($"更新執行結果:{resultUpd}");
            Console.WriteLine("----------------------------");
            // 刪除
            bool resultDel = dal.Delete<Student>(8) > 0 ? true : false;
            Console.WriteLine($"刪除執行結果:{resultDel}");
            List<Student> list2 = dal.QueryAll<Student>();
            Console.WriteLine($"刪除後集合個數:{list2.Count}");
            Console.WriteLine("----------------------------");
            #endregion
            Console.ReadKey();
        }
    }
}

11、結果

注意

在使用SqlDataReader的時候有時會報錯:“已有開啟的與此Command相關聯的DataReader,必須先將它關閉”。

同時開啟兩個或迴圈多個sqldatareader會出現以上錯誤。因為用的是sqldatareader做資料庫的資料讀取,sqlconnection開啟沒有關閉。

一個SqlConnection只能執行一次事務,沒用一次必須關閉然後再開啟。上面我只用了一次沒有關閉,直接開啟所以會報錯。解決方案有如下兩種:

1、其實不用多次開啟在開啟,那樣實現起來很麻煩。直接在連線字串的後面加上MultipleActiveResultSets=true即可。 配置檔案定義如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <!--<add name="DbConnection" connectionString="Server=.;Initial Catalog=MyDb;User ID=sa;Password=123456;MultipleActiveResultSets=True"/>-->
    <!--配置檔案裡面新增MultipleActiveResultSets=True-->
    <add name="DbConnection" connectionString="Server=.;Initial Catalog=MyDb;User ID=sa;Password=123456;MultipleActiveResultSets=True"/>
  </connectionStrings>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
</configuration>

 2、使用DataTable

在上面是使用的SqlDataReader讀取資料,然後轉換成List<T>,可以用DataTable代替SqlDataReader,這樣就不會報錯了,程式碼如下:

/// <summary>
/// 將DataTable轉換成List
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dt"></param>
/// <returns></returns>
private List<T> ConvertToList<T>(DataTable dt) where T:BaseModel
{
      Type type = typeof(T);
      List<T> list = new List<T>();
      foreach(DataRow dr in dt.Rows)
      {
          T t = (T)Activator.CreateInstance(type);
          foreach(PropertyInfo prop in type.GetProperties())
          {
               object value = dr[prop.GetColumnName()];
               if(value is DBNull)
               {
                    value = null;
               }
               prop.SetValue(t, value);
          }
          list.Add(t);
        }
        return list;
}