欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#泛型学习笔记

程序员文章站 2022-06-14 20:07:36
本笔记摘抄自:https://www.cnblogs.com/dotnet261010/p/9034594.html,记录一下学习过程以备后续查用。 一、什么是泛型 泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。泛型类就类似于一个模板,可以在需要时为这个模板传入任何我们需 ......

   本笔记摘抄自:,记录一下学习过程以备后续查用。

    一、什么是泛型

    泛型是c#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。泛型类就类似于一个模板,可以在需要时为这个模板传入任何我们需要的类型。

    二、为什么使用泛型

    下面代码演示输出几种类型的相关信息:

    class program
    {
        /// <summary>
        /// 打印帮助类
        /// </summary>
        public class showhelper
        {
            /// <summary>
            /// showint
            /// </summary>
            /// <param name="intparam"></param>
            public static void showint(int intparam)
            {
                console.writeline($"class={typeof(showhelper).name},type={intparam.gettype().name},parameter={intparam}");
            }

            /// <summary>
            /// showstring
            /// </summary>
            /// <param name="strparam"></param>
            public static void showstring(string strparam)
            {
                console.writeline($"class={typeof(showhelper).name},type={strparam.gettype().name},parameter={strparam}");
            }

            /// <summary>
            /// showdatetime
            /// </summary>
            /// <param name="dtparam"></param>
            public static void showdatetime(datetime dtparam)
            {
                console.writeline($"class={typeof(showhelper).name},type={dtparam.gettype().name},parameter={dtparam}");
            }
        }

        static void main(string[] args)
        {
            #region 非泛型打印方式一
            showhelper.showint(123);
            showhelper.showstring("hello world.");
            showhelper.showdatetime(datetime.now);
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    上面3个方法很相似,除了参数类型不同外,实现的功能是一样的,可以稍作优化。

    下面代码演示使用继承的方式输出几种类型的相关信息:

    class program
    {
        /// <summary>
        /// 打印帮助类
        /// </summary>
        public class showhelper
        {
            /// <summary>
            /// showtype
            /// </summary>
            /// <param name="obj"></param>
            public static void showtype(object obj)
            {
                console.writeline($"class={typeof(showhelper).name},type={obj.gettype().name},parameter={obj}");
            }
        }

        static void main(string[] args)
        {
            #region 非泛型打印方式二
            showhelper.showtype(123);
            showhelper.showtype("hello world.");
            showhelper.showtype(datetime.now);
            console.read();
            #endregion
        }
    }

C#泛型学习笔记

    功能实现没有问题,只是object与其它类型的转换,涉及到装箱和拆箱的过程,这个是会损耗程序的性能的。

    三、泛型类型参数

    在泛型类型或方法的定义中,泛型类型参数可认为是特定类型的占位符。

    下面代码演示使用泛型的方式输出几种类型的相关信息:

    class program
    {
        /// <summary>
        /// 打印帮助类
        /// </summary>
        public class showhelper
        {
            /// <summary>
            /// show
            /// </summary>
            /// <param name="obj"></param>
            public static void show<t>(t tparam)
            {
                console.writeline($"class={typeof(showhelper).name},type={tparam.gettype().name},parameter={tparam}");
            }
        }

        static void main(string[] args)
        {
            #region 泛型打印方式
            showhelper.show(123);
            showhelper.show("hello world.");
            showhelper.show(datetime.now);
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    1、为什么泛型可以解决上面的问题呢?

    泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到调用的时候才给它指定。 

    2、泛型究竟是如何工作的呢?

    程序执行原理:控制台程序最终会编译成一个exe程序。当exe被点击的时候,会经过jit(即时编译器)的编译,最终生成二进制代码才能被计算机执行。

    泛型工作原理:泛型加入到语法以后,vs自带的编译器做了升级,升级之后编译时若遇到泛型,会做特殊的处理:生成占位符。然后经过jit编译的时候,

会把上面编译生成的占位符替换成具体的数据类型。

    下面代码演示泛型占位符:

    class program
    {
        static void main(string[] args)
        {
            #region 泛型占位符
            console.writeline(typeof(list<>));
            console.writeline(typeof(dictionary<,>));
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    3、泛型性能问题

    下面代码演示泛型性能测试:

    class program
    {
        static void main(string[] args)
        {
            #region 泛型性能测试
            long commontime = 0;
            long objecttime = 0;
            long generictime = 0;
            stopwatch watch = new stopwatch();
            watch.start();
            for (int i = 0; i < 10000; i++)
            {
                showhelper.showint(123);
            }
            watch.stop();
            commontime = watch.elapsedmilliseconds;

            watch.reset();
            watch.start();
            for (int i = 0; i < 10000; i++)
            {
                showhelper.showtype(123);
            }
            watch.stop();
            objecttime = watch.elapsedmilliseconds;

            watch.reset();
            watch.start();
            for (int i = 0; i < 10000; i++)
            {
                showhelper.show(123);
            }
            watch.stop();
            generictime = watch.elapsedmilliseconds;

            console.clear();
            console.writeline($"common time={commontime}ms");
            console.writeline($"object time={objecttime}ms");
            console.writeline($"generic time={generictime}ms");
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    从结果可以看出,泛型的性能是最高的。

    四、泛型类

    下面代码演示泛型类:

    class program
    {
        /// <summary>
        /// 泛型类
        /// </summary>
        /// <typeparam name="t"></typeparam>
        public class genericclass<t>
        {
            public t vart;
        }

        static void main(string[] args)
        {
            #region 泛型类
            //t是int类型
            genericclass<int> genericint = new genericclass<int>
            {
                vart = 123
            };
            console.writeline($"the value of t={genericint.vart}");
            //t是string类型
            genericclass<string> genericstring = new genericclass<string>
            {
                vart = "123"
            };
            console.writeline($"the value of t={genericstring.vart}");
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    五、泛型接口

    注:泛型在声明的时候可以不指定具体的类型,继承的时候也可以不指定具体类型,但是在使用的时候必须指定具体类型。

    下面代码演示泛型接口:

    class program
    {
        /// <summary>
        /// 泛型接口
        /// </summary>
        public interface igenericinterface<t>
        {
            t gett(t t);
        }

        /// <summary>
        /// 泛型接口实现类
        /// </summary>
        /// <param name="args"></param>
        public class genericget<t> : igenericinterface<t>
        {
            t vart;
            public t gett(t t)
            {
                vart = t;
                return vart;
            }
        }

        static void main(string[] args)
        {
            #region 泛型接口
            igenericinterface<int> genericinterface = new genericget<int>();
            var result = genericinterface.gett(123);
            console.writeline($"result={result}");
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    六、泛型委托

    下面代码演示泛型委托:

    class program
    {
        /// <summary>
        /// 泛型委托
        /// </summary>
        /// <typeparam name="t"></typeparam>
        /// <param name="t"></param>
        public delegate void sayhi<t>(t t);

        static void main(string[] args)
        {
            #region 泛型委托
            sayhi<string> sayhi = sayhello;
            sayhi("hello world");
            console.read();
            #endregion
        }

        /// <summary>
        /// sayhello
        /// </summary>
        /// <param name="greeting"></param>
        public static void sayhello(string greeting)
        {
            console.writeline($"{greeting}");
        }
    }

    运行结果如下:

C#泛型学习笔记

    七、泛型约束

    泛型约束,实际上就是约束的类型t,使t必须遵循一定的规则。比如t必须继承自某个类或者t必须实现某个接口等等。

    怎样给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。

    泛型约束总共有五种:

约束 s说明
t:结构 类型参数必须是值类型
t:类 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。
t:new() 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。
t:<基类名> 类型参数必须是指定的基类或派生自指定的基类。
t:<接口名称> 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。

    7.1基类约束

    下面代码演示基类约束:

        /// <summary>
        /// 运动类接口
        /// </summary>
        public interface isports
        {
            void pingpong();
        }

        /// <summary>
        /// 人类基类
        /// </summary>
        public class people
        {
            public string name { get; set; }

            public virtual void greeting()
            {
                console.writeline("hello world.");
            }
        }

        /// <summary>
        /// 中国人
        /// </summary>
        public class chinese : people, isports
        {
            public void finetradition()
            {
                console.writeline("自古以来,中华民族就保持着勤劳的优良传统。");
            }
            public override void greeting()
            {
                console.writeline("吃饭了没?");
            }

            public void pingpong()
            {
                console.writeline("乒乓球是中国的国球。");
            }
        }

        static void main(string[] args)
        {
            #region 泛型约束:基类约束
            chinese chinese = new chinese()
            {
                name = "中国人"
            };
            showpeople(chinese);
            console.read();
            #endregion
        }

        /// <summary>
        /// 基类约束
        /// </summary>
        /// <param name="obj"></param>
        public static void showpeople<t>(t tparam) where t:people
        {
            console.writeline($"{((people)tparam).name}");
        }
    }

    运行结果如下:

C#泛型学习笔记

    注:基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义了,因为sealed类没有子类。

    7.2接口约束

    下面代码演示接口约束:

    class program
    {
        /// <summary>
        /// 运动类接口
        /// </summary>
        public interface isports
        {
            void pingpong();
        }

        /// <summary>
        /// 人类基类
        /// </summary>
        public class people
        {
            public string name { get; set; }

            public virtual void greeting()
            {
                console.writeline("hello world.");
            }
        }

        /// <summary>
        /// 中国人
        /// </summary>
        public class chinese : people, isports
        {
            public void finetradition()
            {
                console.writeline("自古以来,中华民族就保持着勤劳的优良传统。");
            }
            public override void greeting()
            {
                console.writeline("吃饭了没?");
            }

            public void pingpong()
            {
                console.writeline("乒乓球是中国的国球。");
            }
        }

        static void main(string[] args)
        {
            #region 泛型约束:接口约束
            chinese chinese = new chinese()
            {
                name = "中国人"
            };
            getsportsbyinterface(chinese);
            console.read();
            #endregion
        }

        /// <summary>
        /// 接口约束
        /// </summary>
        /// <typeparam name="t"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static t getsportsbyinterface<t>(t t) where t : isports
        {
            t.pingpong();
            return t;
        }
    }

    运行结果如下:

C#泛型学习笔记

    7.3引用类型约束 class

    引用类型约束保证t一定是引用类型的。

    下面代码演示引用类型约束:

    class program
    {
        /// <summary>
        /// 运动类接口
        /// </summary>
        public interface isports
        {
            void pingpong();
        }

        /// <summary>
        /// 人类基类
        /// </summary>
        public class people
        {
            public string name { get; set; }

            public virtual void greeting()
            {
                console.writeline("hello world.");
            }
        }

        /// <summary>
        /// 中国人
        /// </summary>
        public class chinese : people, isports
        {
            public void finetradition()
            {
                console.writeline("自古以来,中华民族就保持着勤劳的优良传统。");
            }
            public override void greeting()
            {
                console.writeline("吃饭了没?");
            }

            public void pingpong()
            {
                console.writeline("乒乓球是中国的国球。");
            }
        }

        static void main(string[] args)
        {
            #region 泛型约束:引用类型约束
            chinese chinese = new chinese()
            {
                name = "中国人"
            };
            getsportsbyclass(chinese);
            console.read();
            #endregion
        }

        /// <summary>
        /// 引用类型约束
        /// </summary>
        /// <typeparam name="t"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static t getsportsbyclass<t>(t t) where t : class
        {
            if (t is isports)
            {
                (t as isports).pingpong();
            }
            return t;
        }
    }

    运行结果如下:

C#泛型学习笔记

    7.4值类型约束 struct

    值类型约束保证t一定是值类型的。

    下面代码演示值类型约束:

    class program
    {
        /// <summary>
        /// 绩效工资
        /// </summary>
        public struct achievement
        {
            public double meritpay { get; set; }
            public string level { get; set; }
            public double reallypay()
            {
                switch (level)
                {
                    case "a":
                        meritpay = meritpay * 1.0;
                        break;
                    case "b":
                        meritpay = meritpay * 0.8;
                        break;
                    case "c":
                        meritpay = meritpay * 0.6;
                        break;
                    case "d":
                        meritpay = 0;
                        break;
                    default:
                        meritpay = 0;
                        break;
                };
                return meritpay;
            }
        }

        static void main(string[] args)
        {
            #region 泛型约束:值类型约束
            achievement achievement = new achievement
            {
                meritpay = 500,
                level = "b"
            };
            var result = getreallypay(achievement).reallypay();
            console.writeline($"reallypay={result}");
            console.read();
            #endregion
        }

        /// <summary>
        /// 值类型约束
        /// </summary>
        /// <typeparam name="t"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static t getreallypay<t>(t t) where t : struct
        {
            return t;
        }
    }

    运行结果如下:

C#泛型学习笔记

    7.5无参数构造函数约束 new() 

    下面代码演示无参数构造函数约束:

    class program
    {
        /// <summary>
        /// 运动类接口
        /// </summary>
        public interface isports
        {
            void pingpong();
        }

        /// <summary>
        /// 人类基类
        /// </summary>
        public class people
        {
            public string name { get; set; }

            public virtual void greeting()
            {
                console.writeline("hello world.");
            }
        }

        /// <summary>
        /// 中国人
        /// </summary>
        public class chinese : people, isports
        {
            public void finetradition()
            {
                console.writeline("自古以来,中华民族就保持着勤劳的优良传统。");
            }
            public override void greeting()
            {
                console.writeline("吃饭了没?");
            }

            public void pingpong()
            {
                console.writeline("乒乓球是中国的国球。");
            }
        }

        /// <summary>
        /// 广东人
        /// </summary>
        public class guangdong : chinese
        {
            public guangdong() { }
            public string dialect { get; set; }
            public void mahjong()
            {
                console.writeline("这麻将上瘾的时候,一个人也说是三缺一呀。");
            }
        }

        static void main(string[] args)
        {
            #region 泛型约束:无参数构造函数约束
            guangdong guangdong = new guangdong()
            {
                name = "广东人"
            };
            getmahjong(guangdong);
            console.read();
            #endregion
        }

        /// <summary>
        /// 无参数构造函数约束
        /// </summary>
        /// <typeparam name="t"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static t getmahjong<t>(t t) where t : people, isports, new()
        {
            if (t is guangdong)
            {
                (t as guangdong).mahjong();
            }
            return t;
        }
    }

    运行结果如下:

C#泛型学习笔记

    从上面可以看出,泛型约束可以有多个,但是有多个泛型约束时,new()约束要放到最后。

    八:泛型的协变和逆变

    协变和逆变是在.net 4.0的时候出现的,只能放在接口或者委托的泛型参数前面,out协变covariant,用来修饰返回值;in:逆变contravariant,用来修饰

传入参数。

    下面代码演示父类与子类的声明方式:

    class program
    {
        /// <summary>
        /// 动物基类
        /// </summary>
        public class animal
        {
            public int breed { get; set; }
        }

        /// <summary>
        /// 猫类
        /// </summary>
        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
        }
    }

    以上代码是可以正常运行的。假如使用下面的声明方式,是否正确呢?

list<animal> list = new list<cat>();

    答案是错误的,因为list<animal>和list<cat>之间没有父子关系。

    解决方法是使用协变的方式:

ienumerable<animal> list1 = new list<animal>();
ienumerable<animal> list2 = new list<cat>();

    按f12查看ienumerable定义:

C#泛型学习笔记

    可以看到,在泛型接口的t前面有一个out关键字修饰,而且t只能是返回值类型,不能作为参数类型,这就是协变。使用协变以后,左边声明的是基类,

右边的声明可以是基类或者基类的子类。

    协变除了可以用在接口上面外,还可以用在委托上面:

func<animal> func = new func<cat>(() => null);

    除了使用.net框架定义好协变以外,我们也可以自定义协变:

//使用自定义协变
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>();

    协变和逆变也可以同时使用。

    下面代码演示自定义协变与逆变:

    class program
    {
        /// <summary>
        /// 动物基类
        /// </summary>
        public class animal
        {
            public int breed { get; set; }
        }

        /// <summary>
        /// 猫类
        /// </summary>
        public class cat : animal
        {
            public string name { get; set; }
        }

        #region 泛型的自定义协变和逆变
        /// <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);
            }
        }
        #endregion

        static void main(string[] args)
        {
            #region 泛型的自定义协变与逆变
            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>();       //逆变+协变
            mylist1.get();
            mylist2.get();
            mylist3.get();
            mylist4.get();
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    九、泛型缓存

    类中的静态类型无论实例化多少次,在内存中只会有一个,静态构造函数只会执行一次。在泛型类中,t类型不同,每个不同的t类型,都会产生一个不同

的副本,所以会产生不同的静态属性、不同的静态构造函数。

    下面代码演示泛型缓存:

    class program
    {
        /// <summary>
        /// 泛型缓存
        /// </summary>
        /// <typeparam name="t"></typeparam>
        public class genericcache<t>
        {
            private static readonly string typetime = "";
            static genericcache()
            {
                console.writeline("这个是泛型缓存的静态构造函数:");
                typetime = string.format("{0}_{1}", typeof(t).fullname, datetime.now.tostring("yyyymmddhhmmss.fff"));
            }
            public static string getcache()
            {
                return typetime;
            }
        }

        /// <summary>
        /// 泛型缓存测试类
        /// </summary>
        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);
                }
            }
        }

        static void main(string[] args)
        {
            #region 泛型缓存
            genericcachetest.show();
            console.read();
            #endregion
        }
    }

    运行结果如下:

C#泛型学习笔记

    从上面的截图中可以看出,泛型会为不同的类型都创建一个副本,因此静态构造函数会执行5次,另外每次静态属性的值都是一样的。利用泛型的这一特性,可以实现缓存。

    注:只能为不同的类型缓存一次;泛型缓存比字典缓存效率高;泛型缓存不能主动释放。