C#基礎篇——泛型

来源:https://www.cnblogs.com/i3yuan/archive/2020/05/31/12997374.html

前言 在開發編程中,我們經常會遇到功能非常相似的功能模塊,只是他們的處理的數據不一樣,所以我們會分別採用多個方法來處理不同的數據類型。但是這個時候,我們就會想一個問題,有沒有辦法實現利用同一個方法來傳遞不同種類型的參數呢? 這個時候,泛型也就因運而生,專門來解決這個問題的。 泛型是在C 2.0就推出 ...


前言

在開發編程中,我們經常會遇到功能非常相似的功能模塊,只是他們的處理的數據不一樣,所以我們會分別採用多個方法來處理不同的數據類型。但是這個時候,我們就會想一個問題,有沒有辦法實現利用同一個方法來傳遞不同種類型的參數呢?

這個時候,泛型也就因運而生,專門來解決這個問題的。

泛型是在C#2.0就推出的一個新語法,由框架升級提供的功能。

說明

泛型通過參數化類型實現在同一份代碼上操作多種數據類型。例如使用泛型的類型參數T,定義一個類Stack

可以用Stack、Stack或者Stack實例化它,從而使類Stack可以處理int、string、Person類型數據。這樣可以避免運行時類型轉換或封箱操作的代價和風險。泛型提醒的是將具體的東西模糊化。

同時使用泛型類型可以最大限度地重用代碼、保護類型安全以及提高性能。

可以創建:泛型介面泛型類泛型方法泛型事件泛型委托

開始

泛型類

泛型類封裝不特定於特定數據類型的操作。 泛型類最常見用法是用於鏈接列表、哈希表、堆棧、隊列和樹等集合。 無論存儲數據的類型如何,添加項和從集合刪除項等操作的執行方式基本相同。

    static void Main(string[] args)
    {

        // T是int類型
        GenericClass<int> genericInt = new GenericClass<int>();
        genericInt._T = 123;
        // T是string類型
        GenericClass<string> genericString = new GenericClass<string>();
        genericString._T = "123";

    }

新建一個GenericClass類

    /// <summary>
    /// 泛型類
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GenericClass<T>
    {
        public T _T;
    }

泛型方法

泛型方法是通過類型參數聲明的方法, 解決用一個方法,滿足不同參數類型

    static void Main(string[] args)
    {
        #region 泛型方法
        Console.WriteLine("************Generic**************");
        int iValue = 123;
        string sValue = "456";
        DateTime dtValue = DateTime.Now;
        object oValue = "MrValue";
        GenericMethod.Show<int>(iValue);//需要指定類型參數
        //GenericMethod.Show<string>(iValue);//必須吻合
        GenericMethod.Show(iValue);//能省略,自動推算
        GenericMethod.Show<string>(sValue);
        GenericMethod.Show<DateTime>(dtValue);
        GenericMethod.Show<object>(oValue);
        #endregion

    }

新建一個GenericMethod

/// <summary>
/// 泛型方法
/// </summary>
public class GenericMethod
{
    /// <summary>
    /// 2.0推出的新語法
    /// 泛型方法解決用一個方法,滿足不同參數類型;做相同的事兒
    /// 沒有寫死參數類型,調用的時候才指定的類型
    /// 延遲聲明:把參數類型的聲明推遲到調用
    /// 推遲一切可以推遲的~~  延遲思想
    /// 不是語法糖,而是2.0由框架升級提供的功能
    /// 需要編譯器支持+JIT支持
    /// </summary>
    /// <typeparam name="T">T/S 不要用關鍵字  也不要跟別的類型衝突 </typeparam>
    /// <param name="tParameter"></param>
    public static void Show<T>(T tParameter)
    {
        Console.WriteLine("This is {0},parameter={1},type={2}",
            typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());
    }
}

泛型介面

為泛型集合類或表示集合中的項的泛型類定義介面通常很有用處。在c#中,通過尖括弧“<>”將類型參數括起來,表示泛型。聲明泛型介面時,與聲明一般介面的唯一區別是增加了一個。一般來說,聲明泛型介面與聲明非泛型介面遵循相同的規則。

泛型介面定義完成之後,就要定義此介面的子類。定義泛型介面的子類有以下兩種方法。

(1)直接在子類後聲明泛型。

(2)在子類實現的介面中明確的給出泛型類型。

    static void Main(string[] args)
    {
        #region 泛型介面
        CommonInterface commonInterface = new CommonInterface();
        commonInterface.GetT("123");
        #endregion
    }

新建GenericInterface.cs類文件

        /// <summary>
        /// 泛型類
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class GenericClass<T>
        {
            public T _T;
        }

        /// <summary>
        /// 泛型介面
        /// </summary>
        public interface IGenericInterface<T>
        {
            //泛型類型的返回值
            T GetT(T t);
        }


        /// <summary>
        /// 使用泛型的時候必須指定具體類型,
        /// 這裡的具體類型是int
        /// </summary>
        public class CommonClass : GenericClass<int>
        {

        }

        /// <summary>
        /// 必須指定具體類型
        /// </summary>
        public class CommonInterface : IGenericInterface<string>
        {
            public string GetT(string t)
            {
                return t;
            }
        }

        /// <summary>
        /// 子類也是泛型的,繼承的時候可以不指定具體類型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class CommonClassChild<T> : GenericClass<T>
        {

        }

泛型委托

泛型委托主要是想講一下Action和Func兩個委托,因為這兩個在Linq中是經常見到的。

Action只能委托必須是無返回值的方法

Fun只是委托必須有返回值的方法

不管是不是泛型委托,只要是委托委托那能用Lamdba表達式,因為不管Lamdba表達式還是匿名函數其實都是將函數變數化。

下麵簡單的來做的demo說下兩個的用法,這個會了基本linq會了一半了。

    static void Main(string[] args)
    {
        #region 泛型委托
        Action<string> action = s => {
            Console.WriteLine(s);
        };
        action("i3yuan");
        Func<int, int, int> func = (int a, int b) => {
            return a + b;
        };
        Console.WriteLine("sum:{0}", func(1,1));
        Console.ReadLine();
        #endregion
    }

上面其實都是將函數做為變數,這也是委托的思想。action是實例化了一個只有一個字元串參數沒有返回值得函數變數。func是實例化了一個有兩個int類型的參數返回值為int的函數變數。

可以看到通過Lamdba表達式和泛型的結合,算是又方便了開發者們,更加方便實用。

引入委托常用的另一方式

無論是在類定義內還是類定義外,委托可以定義自己的類型參數。引用泛型委托的代碼可以指定類型參數來創建一個封閉構造類型,這和實例化泛型類或調用泛型方法一樣,如下例所示:

public delegate void MyDelegate<T>(T item);
public void Notify(int i){}
//...
 
MyDelegate<int> m = new MyDelegate<int>(Notify);
 
C#2.0版有個新特性稱為方法組轉換(method group conversion),具體代理和泛型代理類型都可以使用。用方法組轉換可以把上面一行寫做簡化語法:
MyDelegate<int> m = Notify;
 
在泛型類中定義的委托,可以與類的方法一樣地使用泛型類的類型參數。
class Stack<T>
{
T[] items;
      int index
//...
public delegate void StackDelegate(T[] items);
}
 
引用委托的代碼必須要指定所在類的類型參數,如下:
 
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate myDelegate = StackNotify;
 
 
泛型委托在定義基於典型設計模式的事件時特別有用。因為sender[JX2] ,而再也不用與Object相互轉換。
public void StackEventHandler<T,U>(T sender, U eventArgs);
class Stack<T>
{
    //…
    public class StackEventArgs : EventArgs{...}
    public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
    protected virtual void OnStackChanged(StackEventArgs a)
    {
      stackEvent(this, a);
    }
}
class MyClass
{
  public static void HandleStackChange<T>(Stack<T> stack, StackEventArgs args){...};
}
Stack<double> s = new Stack<double>();
MyClass mc = new MyClass();
s.StackEventHandler += mc.HandleStackChange;


泛型約束

所謂的泛型約束,實際上就是約束的類型T。使T必須遵循一定的規則。比如T必須繼承自某個類,或者T必須實現某個介面等等。那麼怎麼給泛型指定約束?其實也很簡單,只需要where關鍵字,加上約束的條件。

定義一個People類,裡面有屬性和方法:

    public interface ISports
    {
        void Pingpang();
    }
    public interface IWork
    {
        void Work();
    }
    public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        public void Hi()
        {
            Console.WriteLine("Hi");
        }
    
    }
    public class Chinese : People, ISports, IWork
    {
        public void Tradition()
        {
            Console.WriteLine("仁義禮智信,溫良恭儉讓");
        }
        public void SayHi()
        {
            Console.WriteLine("吃了麽?");
        }
    
        public void Pingpang()
        {
            Console.WriteLine("打乒乓球...");
        }
    
        public void Work()
        {
            throw new NotImplementedException();
        }
    } 
    public class Hubei : Chinese
    {
        public Hubei(int version)
        { }
    
        public string Changjiang { get; set; }
        public void Majiang()
        {
            Console.WriteLine("打麻將啦。。");
        }
    }
    public class Japanese : ISports
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public void Hi()
        {
            Console.WriteLine("Hi");
        }
        public void Pingpang()
        {
            Console.WriteLine("打乒乓球...");
        }
    }

列印方法

    /// <summary>
    /// 列印個object值
    /// 1 object類型是一切類型的父類
    /// 2 通過繼承,子類擁有父類的一切屬性和行為;任何父類出現的地方,都可以用子類來代替
    /// object引用類型  加入傳個值類型int  會有裝箱拆箱  性能損失
    /// 類型不安全
    /// </summary>
    /// <param name="oParameter"></param>
    public static void ShowObject(object oParameter)
    {
        Console.WriteLine("This is {0},parameter={1},type={2}",
            typeof(Constraint), oParameter.GetType().Name, oParameter);

        Console.WriteLine($"{((People)oParameter).Id}_{((People)oParameter).Name}");

    }

在main方法中

    static void Main(string[] args)
    {
        #region  Constraint 介面約束
        Console.WriteLine("************Constraint*****************");
        {
            People people = new People()
            {
                Id = 123,
                Name = "走自己的路"
            };
            Chinese chinese = new Chinese()
            {
                Id = 234,
                Name = "晴天"
            };
            Hubei hubei = new Hubei(123)
            {
                Id = 345,
                Name = "流年"
            };
            Japanese japanese = new Japanese()
            {
                Id = 7654,
                Name = "i3yuan"//
            };
            CommonMethod.ShowObject(people);
            CommonMethod.ShowObject(chinese);
            CommonMethod.ShowObject(hubei);
            CommonMethod.ShowObject(japanese);
  
            Console.ReadLine();
        }
        #endregion
    }

泛型約束總共有五種。

約束 說明
T:結構 類型參數必須是值類型
T:類 類型參數必須是引用類型;這一點也適用於任何類、介面、委托或數組類型。
T:new() 類型參數必須具有無參數的公共構造函數。 當與其他約束一起使用時,new() 約束必須最後指定。
T:<基類名> 類型參數必須是指定的基類或派生自指定的基類。
T:<介面名稱> 類型參數必須是指定的介面或實現指定的介面。 可以指定多個介面約束。 約束介面也可以是泛型的。

1、基類約束

上面列印的方法約束T類型必須是People類型。

///


/// 基類約束:約束T必須是People類型或者是People的子類
/// 1 可以使用基類的一切屬性方法---權利
/// 2 強制保證T一定是People或者People的子類---義務

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter) where T : People
        {
            Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
            tParameter.Hi();
        }

註意:

基類約束時,基類不能是密封類,即不能是sealed類。sealed類表示該類不能被繼承,在這裡用作約束就無任何意義,因為sealed類沒有子類。

2、介面約束

        /// <summary>
        /// 介面約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : ISports
        {
            t.Pingpang();
            return t;
        }

3、引用類型約束 class

引用類型約束保證T一定是引用類型的。

        /// 引用類型約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : class
        {
            return t;
        }

4、值類型約束 struct

值類型約束保證T一定是值類型的。

        /// 值類型類型約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : struct
        {
            return t;
        }

5、無參數構造函數約束 new()

        /// new()約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T Get<T>(T t) where T : new()
        {
            return t;
        }

泛型約束也可以同時約束多個,例如:

        /// <summary>
        ///  泛型:不同的參數類型都能進來;任何類型都能過來,你知道我是誰?
        /// 沒有約束,也就沒有自由
        ///  泛型約束--基類約束(不能是sealed):
        /// 1 可以使用基類的一切屬性方法---權利
        /// 2  強制保證T一定是People或者People的子類---義務
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter)
        where T : People, ISports, IWork, new()
        {
            Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
            tParameter.Hi();
            tParameter.Pingpang();
            tParameter.Work();
        }

註意:有多個泛型約束時,new()約束一定是在最後。

泛型的協變和逆變

    public class Animal
    {
        public int Id { get; set; }
    }

    public class Cat : Animal
    {
        public string Name { get; set; }
    }
    

 static void Main(string[] args)
 {
    #region 協變和逆變

    // 直接聲明Animal類
    Animal animal = new Animal();
    // 直接聲明Cat類
    Cat cat = new Cat();
    // 聲明子類對象指向父類
    Animal animal2 = new Cat();
    // 聲明Animal類的集合
    List<Animal> listAnimal = new List<Animal>();
    // 聲明Cat類的集合
    List<Cat> listCat = new List<Cat>();

    #endregion
 }

那麼問題來了:下麵的一句代碼是不是正確的呢?

1 List<Animal> list = new List<Cat>();

可能有人會認為是正確的:因為一隻Cat屬於Animal,那麼一群Cat也應該屬於Animal啊。但是實際上這樣聲明是錯誤的:因為List和List之間沒有父子關係。

image-2020053023015097

這時就可以用到協變和逆變了。

1 // 協變
2 IEnumerable<Animal> List1 = new List<Animal>();
3 IEnumerable<Animal> List2 = new List<Cat>();

F12查看定義:

可以看到,在泛型介面的T前面有一個out關鍵字修飾,而且T只能是返回值類型,不能作為參數類型,這就是協變。使用了協變以後,左邊聲明的是基類,右邊可以聲明基類或者基類的子類。

協變除了可以用在介面上面,也可以用在委托上面:

 Func<Animal> func = new Func<Cat>(() => null);

除了使用.NET框架定義好的以為,我們還可以自定義協變,例如:

    /// <summary>
    /// out 協變 只能是返回結果
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICustomerListOut<out T>
    {
        T Get();
    }

    public class CustomerListOut<T> : ICustomerListOut<T>
    {
        public T Get()
        {
            return default(T);
        }
    }

使用自定義的協變:

 // 使用自定義協變
 ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
 ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();

在來看看逆變。

在泛型介面的T前面有一個In關鍵字修飾,而且T只能方法參數,不能作為返回值類型,這就是逆變。請看下麵的自定義逆變:

    /// <summary>
    /// 逆變 只能是方法參數
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICustomerListIn<in T>
    {
        void Show(T t);
    }

    public class CustomerListIn<T> : ICustomerListIn<T>
    {
        public void Show(T t)
        {
        }
    }

使用自定義逆變:

 // 使用自定義逆變
 ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>();
 ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();

協變和逆變也可以同時使用,看看下麵的例子:

    /// <summary>
    /// inT 逆變
    /// outT 協變
    /// </summary>
    /// <typeparam name="inT"></typeparam>
    /// <typeparam name="outT"></typeparam>
    public interface IMyList<in inT, out outT>
    {
        void Show(inT t);
        outT Get();
        outT Do(inT t);
    }

    public class MyList<T1, T2> : IMyList<T1, T2>
    {

        public void Show(T1 t)
        {
            Console.WriteLine(t.GetType().Name);
        }

        public T2 Get()
        {
            Console.WriteLine(typeof(T2).Name);
            return default(T2);
        }

        public T2 Do(T1 t)
        {
            Console.WriteLine(t.GetType().Name);
            Console.WriteLine(typeof(T2).Name);
            return default(T2);
        }
    }

使用:

 IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>();
 IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();//協變
 IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();//逆變
 IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();//逆變+協變

有關可變性的註意事項

  • 變化只適用於引用類型,因為不能直接從值類型派生其他類型
  • 顯示變化使用in和out關鍵字只適用於委托和介面,不適用於類、結構和方法
  • 不包括in和out關鍵字的委托和介面類型參數叫做不變

泛型緩存

在前面我們學習過,類中的靜態類型無論實例化多少次,在記憶體中只會有一個。靜態構造函數只會執行一次。在泛型類中,T類型不同,每個不同的T類型,都會產生一個不同的副本,所以會產生不同的靜態屬性、不同的靜態構造函數,請看下麵的例子:

public class GenericCache<T>
{
    static GenericCache()
    {
        Console.WriteLine("This is GenericCache 靜態構造函數");
        _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
    }

    private static string _TypeTime = "";

    public static string GetCache()
    {
        return _TypeTime;
    }
}
public class GenericCacheTest
{
    public static void Show()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(GenericCache<int>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<long>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<DateTime>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<string>.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache<GenericCacheTest>.GetCache());
            Thread.Sleep(10);
        }
    }
}

Main()方法裡面調用:

 static void Main(string[] args)
 {
    #region 泛型緩存
	GenericCacheTest.Show();
    #endregion
 }

結果:

20200530232600809

從上面的截圖中可以看出,泛型會為不同的類型都創建一個副本,所以靜態構造函數會執行5次。 而且每次靜態屬性的值都是一樣的。利用泛型的這一特性,可以實現緩存。

註意:只能為不同的類型緩存一次。泛型緩存比字典緩存效率高。泛型緩存不能主動釋放。

註意

1.泛型代碼中的 default 關鍵字

在泛型類和泛型方法中會出現的一個問題是,如何把預設值賦給參數化類型,此時無法預先知道以下兩點:

  • T將是值類型還是引用類型

  • 如果T是值類型,那麼T將是數值還是結構

對於一個參數化類型T的變數t,僅當T是引用類型時,t = null語句才是合法的; t = 0只對數值的有效,而對結構則不行。這個問題的解決辦法是用default關鍵字,它對引用類型返回空,對值類型的數值型返回零。而對於結構,它將返回結構每個成員,並根據成員是值類型還是引用類型,返回零或空。下麵GenericList類的例子顯示瞭如何使用default關鍵字。

    static void Main(string[] args)
    {
        #region 泛型代碼預設關鍵字default
        // 使用非空的整數列表進行測試.
        GenericList<int> gll = new GenericList<int>();
        gll.AddNode(5);
        gll.AddNode(4);
        gll.AddNode(3);
        int intVal = gll.GetLast();
        // 下麵一行顯示5.
        Console.WriteLine(intVal);

        // 用一個空的整數列表進行測試.
        GenericList<int> gll2 = new GenericList<int>();
        intVal = gll2.GetLast();
        // 下麵一行顯示0.
        Console.WriteLine(intVal);

        // 使用非空字元串列表進行測試.
        GenericList<string> gll3 = new GenericList<string>();
        gll3.AddNode("five");
        gll3.AddNode("four");
        string sVal = gll3.GetLast();
        // 下麵一行顯示five.
        Console.WriteLine(sVal);

        // 使用一個空字元串列表進行測試.
        GenericList<string> gll4 = new GenericList<string>();
        sVal = gll4.GetLast();
        // 下麵一行顯示一條空白行.
        Console.WriteLine(sVal);
        #endregion
        Console.ReadKey();
    }
    public class GenericList<T>
    {
        private class Node
        {
            // 每個節點都有一個指向列表中的下一個節點的引用.
            public Node Next;
            // 每個節點都有一個T類型的值.
            public T Data;
        }

        // 這個列表最初是空的.
        private Node head = null;

        // 在列表開始的時候添加一個節點,用t作為它的數據值.
        public void AddNode(T t)
        {
            Node newNode = new Node();
            newNode.Next = head;
            newNode.Data = t;
            head = newNode;
        }

        // 下麵的方法返回存儲在最後一個節點中的數據值列表. 如果列表是空的, 返回類型T的預設值.
        public T GetLast()
        {
            // 臨時變數的值作為方法的值返回. 
            // 下麵的聲明初始化了臨時的溫度 
            // 類型T的預設值. 如果該列表為空返回預設值.
            T temp = default(T);

            Node current = head;
            while (current != null)
            {
                temp = current.Data;
                current = current.Next;
            }
            return temp;
        }
    }

2.泛型集合

通常情況下,建議您使用泛型集合,因為這樣可以獲得類型安全的直接優點而不需要從基集合類型派生並實現類型特定的成員。下麵的泛型類型對應於現有的集合類型:

1、List 是對應於 ArrayList 的泛型類。
2、Dictionary 是對應於 Hashtable 的泛型類。
3、Collection 是對應於 CollectionBase 的泛型類。
4、ReadOnlyCollection 是對應於 ReadOnlyCollectionBase 的泛型類。
5、QueueStackSortedList 泛型類分別對應於與其同名的非泛型類。
6、LinkedList 是一個通用鏈接列表,它提供運算複雜度為 O(1) 的插入和移除操作。
7、SortedDictionary 是一個排序的字典,其插入和檢索操作的運算複雜度為 O(log n),這使得它成為 SortedList 的十分有用的替代類型。
8、KeyedCollection 是介於列表和字典之間的混合類型,它提供了一種存儲包含自己鍵的對象的方法。

總結

  1. 作為一個開發人員,當我們程式代碼有相同的邏輯,有可能是方法、介面、類或者委托,只是某些參數類型不同,我們希望代碼可以通用、復用,甚至是說為了偷懶,也可以說是在不確定類型的情況下,就應該考慮用泛型的思維去實現。
  2. 在非泛型編程中,雖然所有的東西都可以作為Object傳遞,但是在傳遞的過程中免不了要進行類型轉換。而類型轉換在運行時是不安全的。使用泛型編程將可以減少不必要的類型轉換,從而提高安全性。不僅是值類型,引用類型也存在這樣的問題,因此有必要的儘量的去使用泛型集合。
  3. 在非泛型編程中,將簡單類型作為Object傳遞時會引起裝箱和拆箱的操作,這兩個過程都是具有很大開銷的。使用泛型編程就不必進行裝箱和拆箱操作了。

參考 文檔 《C#圖解教程》

註:搜索關註公眾號【DotNet技術谷】--回覆【C#圖解】,可獲取 C#圖解教程文件


您的分享是我們最大的動力!

更多相關文章
  • 註釋是為了使別人能看懂你寫的程式,也為了使你在若幹年後還能看得懂你曾經寫的程式而設定的。 註釋是寫給程式員看的,不是寫給電腦看的。所以註釋的內容,C語言編譯器在編譯時會被自動忽略,不會執行註釋的代碼! 方法一:使用// #include "stdafx.h" //main函數 :程式的入口函數,就好 ...
  • 有很多剛學習軟體測試的小伙伴,都會在網路上找尋各種學習資料,去提升自己的專業技能水平。因此,我決定定期分享我整理收集的一些軟體測試的測試工具下載、面試寶典、視頻教學合集。都整理好了,有需要的可以關註我(獲取方式在文末) 軟體測試的學習,不止是基礎理論,還需要學習測試工具的用法,如介面工具Postma ...
  • 女程式員是這麼徵婚的: SELECT * FROM 男人們 WHERE 未婚=true and 同性戀=false and 有房=true and 有車=true and 條件 in (帥氣,紳士,大度,氣質,智慧,溫柔,體貼,會浪漫,活潑,可愛,最好還能帶孩子) and 年齡 between(24 ...
  • API 概述 API(Application Programming Interface),應用程式編程介面。 Java API是一本程式員的 字典 ,是JDK中提供給我們使用的類的說明文檔。 這些類將底層的代碼實現封裝了起來,我們不需要關心這些類是如何實現的,只需要學習這些類如何使用即可。 所以我 ...
  • 0. 前言 該項目使用Maven進行管理和構建,所以需要預先配置好Maven。嗯,在這個系列里就不做過多的介紹了。 1. 創建項目 先創建一個pom.xml 文件,添加以下內容: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http: ...
  • Data StructureThere're two types of variables in C#, reference type and value type.Enum:enum Color{Red=0,Green=1}//equals to enum Color{Red,//start fr... ...
  • 出庫單的功能。能學習了出庫單管理之後,WMS的 主體功能算是完成了。當然一個成熟的WMS還包括了盤點,報表,策略規則,移庫功能及與其他系統(ERP、TMS等)的介面,實現無縫集成,打破信息孤島,讓數據實時、準確和同步。 ...
  • 本文章主要用於介紹在Asp.Net Mvc(C#)中使用Fleck製作一個Html5的即時聊天室,含有完整代碼和演示Demo。 ...
一周排行
  • 本次課程就正式進入開發部分。 首先我們先搭建項目框架,還是和之前漸進式風格保持一致,除必備組件外,儘量使用原生功能以方便大家理解。 開發工具:vs 2019 或以上 資料庫:SQL SERVER 2017 或以上 其他需要用到的我們在項目過程中再提。 一、新建 MVC項目 1、打開VS 2019,C ...
  • 一.背景說明: 之前分享過一個微服務開發框架, “享一個集成.NET Core+Swagger+Consul+Polly+Ocelot+IdentityServer4+Exceptionless+Apollo+SkyWalking的微服務開發框架”,前兩天在Github上收到一個Issues,是想我 ...
  • 前言 Http我們都已經耳熟能詳了,而關於Http學習的文章網上有很多,各個知識點的講解也可說是深入淺出。然而,學習過後,我們對Http還是一知半解。問題出在了哪? Http是一個客戶機與伺服器之間的通信的協議,真的想學習Http,就必須把客戶機和伺服器也學了,也就是說,必須立體的學習,不然我們永遠 ...
  • 本人製作的這個 “簡易日誌 (SimpleLogger)” 包裡面包含的代碼邏輯,最開始也就是簡單地寫入文本,後來經過實際使用的演化,做了各種優化,添加了一些實用的特性,感覺用著還不錯。正所謂獨樂樂不如眾樂樂,於是將其打包上傳到微軟的包管理庫 NuGet 中,大家可以使用試試,相互交流。 ...
  • 1.需求示意圖 2.需求描述 原本是為了給做unity3d客戶端開發的同事提供不定時的消息推送,比如商城購買道具後服務端將道具信息推送給客戶端。 本篇文章簡化理解,用“相關部門開展活動,向全市人民徵集社會服務改善意見”為例子。但核心想法一致:單向推送(指這個需求上只需要單向)。所以這個功能並不是聊天 ...
  • 找到項目中ServiceStack.Text.dll文件的版本,比如我的版本是5.0.0,到GitHub上下載對應的源碼(https://github.com/ServiceStack/ServiceStack.Text/tags) 打開解決方案,重新生成ServiceStack.Text項目,如果 ...
  • 前言 上一篇文章主要介紹了IL的概念以及基礎的示例代碼,在這一篇文章中我們將通過對象調用看IL。 創建對象與調用方法 class Program { static void Main(string[] args) { var obj = new MyClass(); Console.WriteLin ...
  • abp版本5.9 概述 數據遷移無非就是兩件事情,1、創建資料庫,並根據實體創建對應的表;2、添加一些初始數據 abp的數據遷移也是完成這兩件事,比較特殊的是它是多租戶saas系統,而且支持不同的租戶有獨立的資料庫。所以abp中的遷移要先遷移戶主Host,再遷移租戶Tenant的資料庫 它的遷移定義 ...
  • 本文屬於OData系列 目錄 武裝你的WEBAPI-OData入門 武裝你的WEBAPI-OData便捷查詢 武裝你的WEBAPI-OData分頁查詢 武裝你的WEBAPI-OData資源更新Delta 武裝你的WEBAPI-OData之EDM 武裝你的WEBAPI-OData常見問題 武裝你的WE ...
  • 前言 面試總是會被問到有沒有用過分散式鎖、redis 鎖,大部分讀者平時很少接觸到,所以只能很無奈的回答 “沒有”。本文通過 Spring Boot 整合 redisson 來實現分散式鎖,並結合 demo 測試結果。 首先看下大佬總結的圖 正文 添加依賴 <!--redis--> <depende ...