C#基礎系列——小話泛型

前言:前面兩章介紹了C#的兩個經常使用技術:C#基礎系列——反射筆記 和 C#基礎系列——Attribute特性使用 。這一章來總結下C#泛型技術的使用。據博主的使用經歷,以爲泛型也是爲了重用而生的,而且大部分時候會和反射一塊兒使用。此次仍是打算圍繞WWH(即What、Why、How)來說解。html

 

一、什麼是泛型:經過參數化類型來實如今同一份代碼上操做多種數據類型。利用「參數化類型」將類型抽象化,從而實現靈活的複用。怎麼理解呢,其實根據博主的理解,泛型就是將類型抽象化,使用抽象化的類型或對象去實現某些功能和業務,而後全部須要使用這些功能和業務的具體類型去調用泛型的方法和委託。呵呵,是否是仍是有點暈,彆着急,咱們來個例子:面試

咱們首先來定義一種場景:咱們經過sql語句使用Ado.Net來查詢默認獲得的是弱類型的DataTable、DataReader等,而咱們須要對查詢到的結果集使用lamada表達式進行某些複雜的計算,須要將DataTable轉換爲對應的List<T>集合,首先來定義一個泛型的方法:算法

     public static List<T> GetListByDateTable<T>(DataTable dt)
        {
            List<T> modelList = new List<T>();
            try
            {
                //1.若是DataTable沒有數據則直接返回
                if (dt == null || dt.Rows.Count == 0)
                {
                    return modelList;
                }

                //2.遍歷DataTable填充實體
                var lstCol = dt.Columns;
                foreach (DataRow dr in dt.Rows)
                {
                    T model = default(T);
                    //若是是object(這種通常用於一個實體類表示不了的狀況),則先拼接json再反序列化爲object
                    if (typeof(T).Equals(typeof(object)))
                    {
                        var strJson = "{";
                        foreach(DataColumn oCol in lstCol)
                        {
                            var oAttrValue = Convert.IsDBNull(dr[oCol.ColumnName]) ? null : dr[oCol.ColumnName];
                            strJson += "\"" + oCol.ColumnName + "\":\"" + oAttrValue + "\",";
                        }
                        strJson = strJson.ToString().Trim(',') + "}";
                        model = E2ERes.JavaScriptStrToObj<T>(strJson);
                    }
                    else
                    {
                        model = FillEntityByDT<T>(dt, dr);
                    }
                    modelList.Add(model);
                }
            }
            catch
            { 
                
            }
            return modelList;
        }

        //經過DataTable填充實體類
        private static T FillEntityByDT<T>(DataTable dt, DataRow dr)
        {
            T model = (T)typeof(T).GetConstructor(new System.Type[] { }).Invoke(new object[] { });//反射獲得泛型類的實體
            PropertyInfo[] pro = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            Type type = model.GetType();
            foreach (PropertyInfo propertyInfo in pro)
            {
                if (dt.Columns.Contains(propertyInfo.Name))
                {
                    if (Convert.IsDBNull(dr[propertyInfo.Name]))
                    {
                        continue;
                    }
                    if (!string.IsNullOrEmpty(Convert.ToString(dr[propertyInfo.Name])))
                    {
                        type.GetProperty(propertyInfo.Name).SetValue(model, dr[propertyInfo.Name], null);
                    }
                }
            }
            return model;
        }

有了這個泛型的方法,咱們在轉換DataTable和具體的List<Model>的時候是否是就是一個很好的複用。sql

 

二、爲何要使用泛型:博主記得剛參加工做的前兩年有一次面試的時候就被問到「泛型有什麼優點?」,當時怎麼回答的不記得了,只知道面試不太順利~~爲何要用泛型呢?博主以爲泛型的主要優點有如下幾點:json

(1)保證了類型的安全性:泛型約束了變量的類型,保證了類型的安全性。例如List<int>和ArrayList。List<int>集合只能加入int類型的變量,ArrayList能夠Add任何經常使用類型,編譯的時候不會提示錯誤。數組

(2)避免了沒必要要的裝箱、拆箱操做,提升程序的性能:泛型變量固定了類型,使用的時候就已經知道是值類型仍是引用類型,避免了沒必要要的裝箱、拆箱操做。舉例說明:安全

使用泛型以前,咱們使用object代替。ide

object a=1;//因爲是object類型,會自動進行裝箱操做。

int b=(int)a;//強制轉換,拆箱操做。這樣一去一來,當次數多了之後會影響程序的運行效率。

使用泛型以後函數

public static T GetValue<T>(T a)

{
  return a;
}

public static void Main()

{
  int b=GetValue<int>(1);//使用這個方法的時候已經指定了類型是int,因此不會有裝箱和拆箱的操做。
}

(3)提升方法、算法的重用性。上面的例子基本能說明這個優點。post

 

三、泛型的使用:

(1)泛型方法的使用:這也是博主使用最多的用法之一,像這種泛型方法通常是一些static的通用方法,例如原來項目中用到的一個將DataGridViewRow對象轉換成對應的實體Model的方法以下:

     public static T ToObject<T>(DataGridViewRow item) where T:class
        {
            var model = item.DataBoundItem as T;
            if (model != null)
                return model;
            var dr = item.DataBoundItem as System.Data.DataRowView;
            model = (T)typeof(T).GetConstructor(new System.Type[] { }).Invoke(new object[] { });//反射獲得泛型類的實體
            PropertyInfo[] pro = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            Type type = model.GetType();
            foreach (PropertyInfo propertyInfo in pro)
            {
                if (Convert.IsDBNull(dr[propertyInfo.Name]))
                {
                    continue;
                }
                if (!string.IsNullOrEmpty(Convert.ToString(dr[propertyInfo.Name])))
                {
                    var propertytype = propertyInfo.PropertyType;
                    if (propertytype == typeof(System.Nullable<DateTime>) || propertytype == typeof(DateTime))
                    {
                        type.GetProperty(propertyInfo.Name).SetValue(model, Convert.ToDateTime(dr[propertyInfo.Name]), null);
                    }
                    else if (propertytype == typeof(System.Nullable<decimal>) || propertytype == typeof(decimal))
                    {
                        type.GetProperty(propertyInfo.Name).SetValue(model, Convert.ToDecimal(dr[propertyInfo.Name]), null);
                    }
                    else if (propertytype == typeof(System.Nullable<int>) || propertytype == typeof(int))
                    {
                        type.GetProperty(propertyInfo.Name).SetValue(model, Convert.ToInt32(dr[propertyInfo.Name]), null);
                    }
                    else if (propertytype == typeof(System.Nullable<double>) || propertytype == typeof(double))
                    {
                        type.GetProperty(propertyInfo.Name).SetValue(model, Convert.ToDouble(dr[propertyInfo.Name]), null);
                    }
                    else
                    {
                        type.GetProperty(propertyInfo.Name).SetValue(model, dr[propertyInfo.Name], null);
                    }
                }
            }
            return model;
        }

使用泛型方法的注意事項:

  • 泛型方法的重載:public void Fun1<T>(T a);和public void Fun1<U>(U a);是沒法重載的,這其實很好理解,由於T和U其實都是泛型的一個表明符號而已;
  • 泛型方法的重寫:下面的方法重寫FuncA的重寫是正確的,FuncB的重寫不正確,由於約束被默認繼承,不用再寫。
abstract class BaseClass
{
    public abstract T FuncA<T,U>(T t,U u) where U:T;
    public abstract T FuncB<T>(T t) where T:IComparable;
}
 
class ClassA:BaseClass
{
    public override X FuncA<X,Y>(X x,Y y){...}
    public override T FuncB<T>(T t) where T:IComparable{...}
}

 

(2)泛型類的使用:

public class Class_Base<DTO, T>
{}

使用這個類的時候必需要指定兩個泛型類。

 

(3)泛型接口以及泛型繼承的使用:

泛型接口的類型參數要麼已實例化,要麼來源於實現類聲明的類型參數

public interface Interface_Base<T>
{}

public class Class_Base<DTO, T> : Interface_Base<DTO>
{}

DTO來源於實現類Class_Base

 

(4)泛型委託的使用:其實這種用法博主真的用得不多,只是原來見到過大牛們相似的代碼。

定義泛型委託:

public delegate void MyDelegate<T>(T obj);

泛型委託的使用:

public delegate void MyDelegate<T>(T obj);
static void Main(string[] args)
{
    var method = new MyDelegate<int>(printInt);
    method(1);
    Console.ReadKey();
}
static void printInt(int i)
{
    Console.WriteLine(i);
}

 

(5)泛型約束:用來約束泛型類型有那些特性。常見的泛型約束也就那麼幾類:

泛型約束的格式

public class Imps_Base<DTO, T> : Ifs_Base<DTO>
        where T : BaseEntity
        where DTO : class
    {
  }

 

約束 說明

T:struct

類型參數必須是值類型。能夠指定除 Nullable 之外的任何值類型。

T:class

類型參數必須是引用類型,包括任何類、接口、委託或數組類型。

T:new()

類型參數必須具備無參數的公共構造函數。當與其餘約束一塊兒使用時,new() 約束必須最後指定。

T:<基類名>

類型參數必須是指定的基類或派生自指定的基類。

T:<接口名稱>

類型參數必須是指定的接口或實現指定的接口。能夠指定多個接口約束。約束接口也能夠是泛型的。

T:U

爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數。這稱爲裸類型約束.

相關文章
相關標籤/搜索