C# 学习笔记:泛型

泛型对于我来说一直是个心头大患,俗话说得好:

人类最古老、最强烈的恐惧是对未知的恐惧。

这种恐惧是全方位的,那么我们就从最开始来打破它。

泛型之所以让人害怕,主要是它太过于多变,他很像假面骑士Decade,Decade在各大剧场版和ZIO里面都很强,是因为它能变成其他的假面骑士(平成昭和都能变),你刚知道他的招数,他就变成另一个假面骑士使用其他的能力了。就跟我们的泛型一样,能和其他的类型进行融合,有泛型类、泛型委托、泛型接口、泛型结构、泛型方法。所以我们在给出定义之前,先写泛型的相关例子。

首先做一些前期准备,我们先声明一个空类,这个类不做任何用处,只是拿来做类型的测试,因为一个类就是一个类型。

    public class Game
    {

    }

1.泛型方法

首先我们来个简单的,使用泛型方法来测试一下,我们在方法前面加入一个<T>表示我们需要使用泛型,那么,我们在调用方法的时候要声明我们的T是哪种类型。

    static void Main(string[] args)
        {
            int a = 123;
            FanType fan = new FanType();
            fan.Show<int>(a);
        }
    }

    public class FanType
    {
        public void Show<T>(T parameter)
        {
            Console.WriteLine("当前的类型是"+parameter.GetType());
        }
    }

 那么,很显然,我们输出的结果就是:

那么,如果是泛型的那个“型”是个类呢,仍然是一样的。

我们的代码改成:

    static void Main(string[] args)
        {
            var a = new Game();
            FanType fan = new FanType();
            fan.Show<Game>(a);
        }
    }

    public class FanType
    {
        public void Show<T>(T parameter)
        {
            Game game = new Game();
            if(parameter.GetType()==game.GetType())
            {
                Console.WriteLine("是同一类型");
            }
            else
            {
                Console.WriteLine("不是同一类型");
            }
            Console.WriteLine("当前的类型是"+parameter.GetType());
        }
    }

结果也很显然,我们在函数里面创建了一个Game的实例,两个的Type是一样的。

注意,在泛型方法调用的时候,我们可以不声明类型,传过去T类型就能给到Type类型了。

fan.Show(a);

这就是泛型方法的使用。

2.泛型类

在上面的代码中实现了泛型方法,那么接下来如果要使用泛型类呢,我们先把"<T>"紧接着放在类声明的后面试试。。。

    public class FanType<T>
    {
        public void Foreachfan(T parameter)
        {
            Game game=new Game();
            if (parameter.GetType() == game.GetType())
            {
                Console.WriteLine("是同一类型");
            }
            else
            {
                Console.WriteLine("不是同一类型");
            }
            Console.WriteLine("当前的类型是" + parameter.GetType());
        }
    }
    public class FanTypeSon<T> : FanType<T>
    {
        public void SeeType(T t)
        {
            Console.WriteLine("当前类型是" + t.GetType());
        }
    }

这里我们在父类中定义了一个泛型,那么在子类中,我们还是可以继承它,可以有两种方式:

  •     public class FanTypeSon<T> : FanType<T>
  •     public class FanTypeSon : FanType<string>

这两种都是泛型类的继承,

第一种就是把这个泛型托付给子类,它在创建实例的时候仍然需要知道T的类型。

第二种就是表明泛型里的类型,这样子类就能知道父类方法字段中泛型字段和方法的类型是什么,然后子类直接使用就好,当然,这样自然让子类失去了使用泛型的能力。这种方法在创建的时候若是父类对象指向子类实例,所声明的类型必须与继承时类型一致,例如我上一句里面继承的时候声明了string,我理应这样写:

FanType<string> fan=new FantypeSon();

我们在上面使用了第一种方式继承,主函数中这样写:

        static void Main(string[] args)
        {
            var Instance = new Game();

            FanType<Game> fan = new FanTypeSon<Game>();

            ((FanTypeSon<Game>)fan).SeeType(Instance);
            fan.Foreachfan(Instance);
        }

注意这里,创建了父类的对象并指向子类的实例,但是由于父类中没有子类的方法,所以实例需要类型转换后才能调用子类的方法。输出结果:

3.泛型接口

泛型接口意味着,接口里面的方法的返回值、参数都有可能是泛型的,那么我们写个例子,就拿上面的改一改

    interface IFanType<T>
    {
        void Foreachfan(T parameter);
    }
    public class FanTypeSon<T> : IFanType<T>
    {
        public void Foreachfan(T parameter)
        {
            Game game = new Game();
            if (parameter.GetType() == game.GetType())
            {
                Console.WriteLine("是同一类型");
            }
            else
            {
                Console.WriteLine("不是同一类型");
            }
            Console.WriteLine("当前的类型是" + parameter.GetType());
        }
    }

如同类之间继承一样,接口的子类在继承的时候,也是可以选择是否标明泛型的类型。

主函数这样写:

        static void Main(string[] args)
        {
            var Instance = new Game();

            IFanType<Game> fan = new FanTypeSon<Game>();
            
            fan.Foreachfan(Instance);
        }

接口的实现和类继承很相似。

总结

!概念:

      泛型是允许延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候才被指定确定的类型

!原理:

      当我面的编译器编译的时候遇见泛型,会做特殊的处理:生成一个占位符

      当进行JIT编译的时候,会首先会获取对应的CIL代码,然后使用我们指定的类型替换掉占位符,最后再将替换后的CIL代码编译为原生代码。

!Mono对泛型的优化:

  1. 如果在编译的时候每一个方法+类型的组合都要生成一次原生代码,那么该方法多次调用之后的编译将会臃肿不堪,在Mono中,一个“方法+类型组合”只会编译一次,后面再次出现调用该方法时将直接复用,而不会重新编译。
  2. 在Mono中,所有的引用类型的实参和变量都是完全相同的,它们都被认为是指向托管堆内存中的某个对象的指针,会采用同样的方式来处理。而值类型则必须单独编译成原生代码

!泛型的优点:

不用泛型的缺点:

      在C#中,众所周知的是所有的类的基类都是Object类,那么,如果我要动态确定类型实现代码复用,我不是直接使用Object对象不就好了吗?如下图,两个函数的效果是一致的:

        Object Change(Object a)
        {
            return a;
        }
        T Change<T>(T a)
        {
            return a;
        }

      而使用Object类型的弊病在于,Object类型本身是引用类型。对于引用类型和值类型的内存存放方式不一样,导致如果我们如果使用Object存放一个值类型的时候,会引发装箱操作。例如:

int a=5;
Object b=a

那么,如果说使用Object类型装填一个值类型(int),它会在托管堆中分配内存,返回的是堆中对象引用的地址。这样造成了内存不必要的损耗。而众所周知,频繁的装箱拆箱会造成不必要的性能损耗,拖慢程序的速度(频繁调用堆=》增加GC的次数)。

而泛型的优点就体现出来了:

1.类型安全:泛型中保证了类型的确定,如果类型不兼容就会报错甚至抛出异常。保证了只有与指定数据类型兼容的情况才能用于该泛型类型。

2.性能更好,在传进去参数前参数的类型就已经被动态指定好了,这样不会发生装箱拆箱操作,性能上优于使用Object作为参数或者返回值。

3.代码复用,一个类只需要一次定义,不需要多次声明逻辑相似但是类型不同的代码,实例化后的类型也变得多种多样。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值