淺談C#泛型

大藝術家007發表於2019-07-08

一.為什麼要提出泛型的概念

我們在宣告物件或者方法中,物件中成員變數的定義或者函式引數都傳遞都要指定具體的物件型別,但是有的時候引數的型別是變化的,但是實現的功能卻又差不多,這個時候我們就想,是否存在一種東西可以將引數的位置“佔住”,當傳遞具體的物件型別是再用這個型別取替換被佔住的位置,這個時候就提出了泛型的概念,是不是有點繞,但是看完下面的例子就清除這裡表達的內容了,泛型有多種表現形式,泛型類,泛型方法,泛型集合,泛型委託,可以說不懂泛型就沒有真正的瞭解C#,下面讓我們來開始泛型的學習吧。

二.泛型類,泛型方法

我們先舉個例子,我們定義一個類來模擬入棧出棧操作,我們操作出棧入棧時要針對各種資料型別,int型,double 型,字元型......總之各種型別都有可能,我們不可能針對每個型別都寫一個類來操作出棧入棧,這顯然是不現實的,這個是時候就該泛型大顯身手發時候了,看下面的定義:

 public class MyStack<T>  //多種型別時可以用T1,T2,T3......來表示
    {

        public T[] objStack;
        public int _stackPoint;
        public int _stackSize;
        public MyStack(int Size)   //成員變數一般在初始化的時候都要賦值
        {
            objStack = new T[Size];
            _stackSize = Size;
            _stackPoint = -1;
        }

        /// <summary>
        /// 入棧操作
        /// </summary>
        /// <param name="item"></param>
        public void Push(T item)   //這裡要把T當成一種資料型別
        {
            if (_stackPoint > _stackSize)
            {
                return;
            }
            else
            {
                _stackPoint++;
                objStack[_stackPoint] = item;
            }
        }

        /// <summary>
        /// 出棧操作
        /// </summary>
        /// <returns></returns>
        public T Pop()
        {
            if (_stackPoint > 0)
            {
                _stackPoint--;
                return objStack[_stackPoint];
            }
            else
            {
                return objStack[0];
            }


        }
    }

我們在 public class MyStack<T> 後面加了一個<T>這個時候這個類就變成了一個泛型類,表示一個佔位符,當我們例項化該類的時候需要傳入具體的資料型別,我們來看一下泛型類的具體用法:

    public int[] arrayInt = new int[6];
    public string[] arrayStr = new string[6];
    MyStack<int> objMyIntStack = new MyStack<int>(6);
    MyStack<string> objMyStrStack = new MyStack<string>(6);

這樣泛型類就可以操作int 型別 和 string型別進行出棧入棧操作但程式碼卻不需要改動。

 

三.泛型集合

 使用泛型集合首先是是加了型別安全,方便程式設計,泛型集合指定了型別後只能將同型別的引數放入集合,泛型集合最常用就是List集合和Dictionary集合,我們分別看一下這兩種集合。

A.Lis<T> 泛型集合

說到List泛型集合就不得不說ArrayList集合,ArrayList集合在操作是需要進行強制型別,極大的降低了程式碼處理效率所以,List集合應運而生,讓我們看如下程式碼做個比較:

 

  //用ArrayList集合來儲存
            ArrayList objArrayList = new ArrayList();
            Students stu1 = new Students() { Name="小紅",Age=20};
            Students stu2 = new Students() { Name = "小明", Age = 30 };
            objArrayList.Add(stu1);
            objArrayList.Add(stu2);
            Students obj = (Students)objArrayList[0];
            Console.WriteLine(obj.Name + obj.Age.ToString());    //這裡需要進行強制型別轉換
            Console.ReadLine();
            
            //用List集合來儲存 
            List<Students> objList = new List<Students>() 
            {
            new Students(){Name = "小紅",Age=20},
            new Students(){Name="小明",Age = 30}
            };
            foreach (var item in objList)
            {
                Console.WriteLine(item.Name + "," + item.Age.ToString());
            }
            Console.ReadLine();

除此之外,我們一直在講泛型集合可以保證資料安全,和ArrayList相比它的資料到底安全在什麼地方呢,我們通過下面的例子做進一步說明:

ArrayList objArrayList = new ArrayList();
Students stu1 = new Students() { Name="小紅",Age=20};
Students stu2 = new Students() { Name = "小明", Age = 30 };

Teacher tea1 = new Teacher() { Name = "小剛", Age = 30 };
objArrayList.Add(stu1);
objArrayList.Add(stu2);
objArrayList.Add(tea1);  //Teacher類也可以新增進來,型別不安全
foreach (var item in objArrayList)
{
Students obj00 = (Students)item; 
}

從例子可以看出ArrayList集合的Add方法引數是object型別,所以Teacher的資料型別也可以放進去,這顯然不是我們想要的,但是泛型集合就不一樣,當佔位符被確定的資料型別佔用後,別的資料型別就新增不到集合中去。

List集合的常用方法,List集合中有很多方法,我們重點將一下Sort方法,Sort方法有四個過載方法,public void Sort();,public void Sort(Comparison<T> comparison);,

public void Sort(IComparer<T> comparer);,public void Sort(int index, int count, IComparer<T> comparer);我們直接呼叫Sort方法是按預設升序排序,假如某個類實現了IComparable介面那麼預設排序就是按照介面中定義的方法來排序,看下面的例子:

  //List集合排序
            List<int> intList = new List<int>() { 1, 4, 3, 11, 8, 2, 0 };
            intList.Sort();
            foreach (var item in intList)
            {
                Console.WriteLine(item.ToString());
            }
            Console.ReadLine();

輸出結果為:0,1,2,3......結果為升序排序

 //字串排序
            List<string> objListStr = new List<string>() { "c","a","b"};
            objListStr.Sort();
            foreach (var item in objListStr)
            {
                Console.WriteLine(item);
            }
            Console.ReadLine();

輸出結果為:a,b,c

假如是物件型別呢,預設的排序方法為升序排序,但是物件之間沒有升序的概念,這個時候該怎麼辦呢,看下面的程式碼:

  public class Students : IComparable<Students>
    {
        public string Name { get; set; } 
        public int Age { get; set; }

        /// <summary>
        /// 實現泛型介面
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(Students other)
        {
            return other.Name.CompareTo(this.Name);
        }
    }



static void Main(string[] args)
        {
            Students stu1 = new Students() { Name = "Mick", Age = 20 };
            Students stu2 = new Students() { Name = "Jack", Age = 30 };
            List<Students> objList = new List<Students>();
            objList.Add(stu1);
            objList.Add(stu2);
            objList.Sort();
            foreach (var item in objList)
            {
                Console.WriteLine(item.Name);
            }
            Console.ReadLine();
        }

Students類中實現了泛型介面IComparable<T> ,在泛型介面的方法中我們可以寫排序的方式,這樣做確實可以解決物件排序的問題,但是假如我們的排序條件是變化的,這種方式顯然又不能滿足我們的需求了,讓我i們接著往下探索,如何實現集合物件的動態排序,讓我們看如下程式碼:

    /// <summary>
    /// 按姓名降序排列
    /// </summary>
    public class NameDesc:IComparer<Students>
    {

        public int Compare(Students x, Students y)
        {
          return  y.Name.CompareTo(x.Name);
        }
    }

    /// <summary>
    /// 按姓名升序排序
    /// </summary>
    public class NameAsc : IComparer<Students>
    {
        public int Compare(Students x, Students y)
        {
            return x.Name.CompareTo(y.Name);
        }
    }


    /// <summary>
    /// 按年齡降序
    /// </summary>
    public class AgeDesc:IComparer<Students>
    {
        public int Compare(Students x, Students y)
        {
         return   y.Age - x.Age;
        }
    }

    /// <summary>
    /// 按年齡升序
    /// </summary>
    public class AgeAsc : IComparer<Students>
    {
        public int Compare(Students x, Students y)
        {
            return x.Age.CompareTo(y.Age);
        }
    }

我們定義了一個自定義排序類,自定義排序類實現了ICompare介面。

   static void Main(string[] args)
        {
            Students stu1 = new Students() { Name = "Mick", Age = 20 };
            Students stu2 = new Students() { Name = "Jack", Age = 30 };
            List<Students> objList = new List<Students>();
            objList.Add(stu1);
            objList.Add(stu2);
            objList.Sort(new AgeDesc());   //基於介面實現多型的典型應用
            foreach (var item in objList)
            {
                Console.WriteLine(item.Name);
            }
            Console.ReadLine();
        }

呼叫List.Sort的過載方法,這裡基於介面實現了多型,需要好好體會,關於集合的排序我們還可以用Linq查詢。

B.Drictionary<> 泛型集合

 List集合用索引查詢元素的方法顯然沒有辦法滿足我們的實際需求,為了彌補這個缺陷,我們引入了字典的概念,說到鍵值對查詢又不得不說說Hashtable,早期鍵值對集合都是用Hashtable類來實現的,後來泛型集合出現後Dictionary泛型集合取代了Hashtable類,讓我們來看看兩者的區別:

  //用Hashtable集合
            Hashtable objHashtable = new Hashtable();
            objHashtable.Add("student1", new Students() { Name = "小王", Age = 20 });
            objHashtable.Add("student2", new Students() { Name = "小李", Age = 25 });
            Students stu =(Students)objHashtable["student1"];   //需要進行強制型別轉換


            //用Dictionary集合
            Dictionary<string, Students> objDictionary = new Dictionary<string, Students>();
            objDictionary.Add("student1", new Students() { Name = "小王", Age = 20 });
            objDictionary.Add("student2", new Students() { Name="小李",Age = 25});
            Students myStudent = objDictionary["student1"]; //不需要進行強制型別轉換

從例子可以看出Hashtable集合操作都是object的型別,在進行物件操作是需要進行強制型別轉換,但是Dictionary卻不一樣,不需要進行強制型別轉換,所以可以這樣講Dictionary出現以後可以完全替Hashtable。

四.泛型委託

 A.自定義泛型委託

 static void Main(string[] args)
        {
            Mydelegate<int> objMydelegate = Add;
            Console.WriteLine("結果為:{0}", objMydelegate(1, 2));
            Console.ReadLine();
        }
        static int Add(int i1,int i2)
        {
            return i1 + i2;
        }
    }
    public delegate T Mydelegate<T>(T t1, T t2); //自定義泛型委託

以上例子就簡單展示了自定泛型委託的使用方法,但是每次都這這麼定義委託似乎很不方便,所以微軟的工程師預先給我們定義好了幾個泛型委託,我們可以直接使用,大大提高了使用泛型委託的便捷程度。

B.Func泛型委託的使用

 Func是一個帶返回值的泛型委託,func有多個過載版本,需要注意的是func最後一個引數是返回值型別,如果前面有泛型型別的引數,這個引數就是委託方法的形參型別,簡單說func泛型委託就是一個帶返回值的方法簽名,我們先來看看它的簡單應用:

   static void Main(string[] args)
        {
            Func<int, int, int> objFunc = (a, b) => { return a + b; };
            Console.WriteLine("結果為:{0}", objFunc(2, 5));
            Console.ReadLine();
        }

有人會說這樣用似乎沒什麼意義,我們呼叫方法就可以直接實現功能,幹嘛還要從委託轉一下似乎多此一舉,但是事實並不是如此,讓我們看一下Func的複雜用法。現在提出一個需求,要求計算陣列中任意指定開始位和結束位的“算數和” and 算數積。常規做法是:

 static int GetSum(int[] nums, int from, int to)
        {
            int result = 0;
            for (int i = from; i <= to; i++)
            {
                result += nums[i];
            }
            return result;
        }
        static int GetMulti(int[] nums, int from, int to)
        {
            int result = 1;
            for (int i = from; i <= to; i++)
            {
                result *= nums[i];
            }
            return result;
        }

寫兩個方法,分別計算和與積,但是還有別的實現方法麼,答案是肯定的:

 static void Main(string[] args)
        {
              int[] nums = { 1, 2, 10, 4, 5, 6, 7, 8, 9 };
              Console.WriteLine("陣列前三個元素的和為:{0}", CommonMethod((a, b) => { return a + b; }, nums, 0, 3));
              Console.WriteLine("陣列前三個元素的積為:{0}", CommonMethod((a, b) => { return a * b; }, nums, 0, 3));
              Console.ReadLine();
        }
        static int CommonMethod(Func<int, int, int> com, int[] nums, int a, int b)
        {
            int result = nums[a];
            for (int i = a + 1; i < b; i++)
            {
                result = com(result, nums[i]);
            }
            return result;
        }

其實這裡也體現了委託的本質,委託本來就是為了把方法當成引數傳遞而設計的。

C.Action泛型委託

Action泛型委託和func泛型委託差不多,只不過Action是不帶返回值的方法的簽名,看下面的例子我們就可以瞭解Action泛型委託的用法:

 static void Main(string[] args)
        {
            Action<string> objAction = (a) => { Console.WriteLine(a); };
            objAction("Hello C#");
            Console.ReadLine();
        }

D.Predicate泛型委託

 Predicate<T>委託定義如下:
 public delegate bool Predicate<T>(T obj);
 解釋:此委託返回一個bool值的方法
 在實際開發中,Predicate<T>委託變數引用一個“判斷條件函式”,
 在判斷條件函式內部書寫程式碼表明函式引數所引用的物件應該滿足的條件,條件滿足時返回true

看下面的例子:

 static void Main(string[] args)
        {
            List<int> objList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

            List<int> resultList = objList.FindAll((s) => { return s > 2; }); //Predicate委託

            foreach (var item in resultList)
            {
                Console.WriteLine(item);
            }
            Console.ReadLine();
        }

好的以上就是關於泛型概念的總結,希望可以幫到有需要的人。

 

相關文章