C# 总结 1---泛型

C# 进阶之–泛型


前言

泛型是.NET Framework2.0新增的一个特性,在命名空间System.Collections.Generic,包含了几个新的基于泛型的集合类,官方建议.net 2.0 及更高版本的应用程序使用心得泛型集合类,而不使用非泛型集合类,例如ArrayList。


一、为什么要用泛型

如上所述,在没有使用泛型的情况下,假如我们需要定义一个存在小数的集合类型,那么我们只能使用ArrayList,如果我们忘记了,不小心放入了一个string类型,这个时候编译器不会提示报错。

static void Main(string[] args)
        {
            ArrayList list = new ArrayList();
            list.Add(99.9);
            list.Add(88.8);
            list.Add("3.1415");

            float total = 0;
            foreach (var item in list)
            {
                total += (float)item;
            };
            Console.WriteLine(total);
        }

只有在运行时才能发现错误,除此之外,我们还发现,ArrayList是以object的类型存储数据的,在存储和取出来使用的过程中,装箱和拆箱的操作是无法避免的,这必然会降低程序运行的性能。
而我们使用泛型重新构建下上面的程序如下:

  static void Main(string[] args)
        {
            List<float> list = new List<float>();
            list.Add(99.9f);
            list.Add(88.8f);
         ///  list.Add("3.1415"); //编译会报错。
            float total = 0;
            foreach (var item in list)
            {
                total +=item;
            };
            Console.WriteLine(total);
        }

这个时候,传入list的数据会在编译时就会进行检查,保证了类型的安全性,同时在使用的时候,不需要进行类型的转换。下面我们就一步步走进泛型。

二、泛型有哪些类型

1.泛型类

 面向对象的语言,万物皆对象,我们先从泛型类来进一步了解泛型。
 泛型类主要的定义方式如下:
  [类型修饰符][static] class className <T>     

我们先来定义一个泛型类。

  public class GenericDemo1 <T>
    {
        public T Data { get; set; }

        public void ShowInfo (T info)
        {
            Console.WriteLine(info);
        }

        public T GetInfo(T data)
        {
            return default(T);
        }
    }

其中 T是在实例化类的要传入的参数类型,这里可以是任何类型,
必须int、object、数组,甚至是自己定义的其他类类型,或者

  static void Main(string[] args)
        {
            GenericDemo1<int> gInt = new GenericDemo1<int>(); //基本类型
            GenericDemo1<int[]> gIntArray = new GenericDemo1<int[]>(); //数组类型
            GenericDemo1<List<int>> gList = new GenericDemo1<List<int>>();//列表类型
            GenericDemo1<Dictionary<string, string>> gdictonary = new GenericDemo1<Dictionary<string, string>>();//字典类型
            GenericDemo1<Comparer> g = new GenericDemo1<Comparer>();//传入类类型
        }

除此之外,如果一个类有几种不同的类型,可以定义多个占位符

  public class GenericDemo1<T,U> //这个时候你可以传入相同的类型或者不同的类型
  例如
  GenericDemo1<int, int> genericDemo1 = new GenericDemo1<int, int>();
  GenericDemo1<int, string> genericDemo2 = new GenericDemo1<int, string>();

泛型类继承主要分为两类:

1.如果子类是普通类,则继承泛型类的时候,必须明确泛型类的类型
2.如果子类也是泛型类,则继承泛型类的时候,必须和父类的泛型类型保持一致。
 //子类是普通类,必须指明确定类型
 public class ChildrenGeneric:GenericDemo1<String>
  
 //子类是泛型类,必须和父类保持一致,也可以加新的泛型类类型
  public class ChildrenGeneric<T,V>:GenericDemo1<T>

2.泛型方法

泛型方法和泛型类没有直接关系,
1、泛型方法不必在泛型类中,也可以在普通类中
2、泛型方法可以是静态方法,也可以是非静态方法

  public class GenericDemo2
    { 

        //带返回的泛型方法
        public T GetInfo<T>(T data)
        {
            return default(T);
        }
        //不带返回值的泛型方法
        public void Show<T>(T data)
        {
            Console.WriteLine(data.ToString());
        }

        //静态泛型方法
        public static T ShowInfo<T>(T info)
        {
            return info;
        }        
    }

泛型类中的T和泛型方法中的T没有直接联系,如下:

  public class GenericDemo1 <T>
    {
        public T Data { get; set; }
        public T ShowInfo<T> (T info)
        {
            return info;
        }
        public T GetInfo()
        {
            return Data;
        }
    }

测试结果如下:

 static void Main(string[] args)
        {
            GenericDemo1<int> gInt = new GenericDemo1<int>(); //基本类型
            gInt.Data = 1124610;

            Console.WriteLine(gInt.GetInfo());//打印泛型类的T信息---结果为1124610
            Console.WriteLine(gInt.ShowInfo<String>("中国最美丽!"));//泛型方法的T信息---结果为"中国最美丽!"
            
        }

3.泛型接口

 public interface GenericInterface<T>
 {
     T Show(T data);
 }

泛型接口的继承主要分为两类:

1.如果实现类不是泛型类,必须指定泛型接口的具体类型
2.如果实现类是泛型类,必须和泛型接口保持一致,也可以增加新的占位符。

4.泛型委托

    public delegate T Do<T>();
    public delegate void Work<T>(); 

C#在定义了两个常用的泛型委托供我们使用,

  1、不带返回值得委托,Action<T>
  2、带返回值的委托   Func<T,V>(); //V才是返回值的类型

三、泛型的约束

有时候在使用泛型时,需要将泛型的类型约束在一定的范围内使用。可以对泛型类或泛型接口,方法能够接收的类型参数的种类加以限制。在编译阶段,如果使用不符合要求的类作为类型参数,则会产生编译错误。进一步来保证类型的安全。
泛型的约束主要分为:

  • 基类约束
  • 值类型、引用类型约束
  • 接口约束
  • new()构造函数约束
  • 组合约束

定义如下类:

     public interface Sing
    {
        void SingSong();
    }
    public class Animal
    {
        public string Name { get; set; }
    }

    public class Fish:Animal
    {
        public string Taste { get; set; }
        public void Swim()
        {

        }
    }

    //金鱼
    public class GoldFish : Fish
    {
    }

    //鲤鱼
    public class Carp : Fish
    {

    }

    public class Dolphin : Fish, Sing
    {
        public void SingSong()
        {
            Console.WriteLine("I can sing song");
        }
    }

定义如下一个泛型方法:

//基类约束,只能是继承了Fish的类型
  public static void Cook<T> ( T food ) where T:Fish
  {
     Console.WriteLine("this fish taste is:"+food.Taste);
  }
  //接口约束 只能是继承了Sing接口的类型
  public static void ListenSong<T> ( T fish) where T:Sing
  {
  }

 //组合约束 只能是继承了Sing接口的类型和Fish类型的,接口必须在后面。
  public static void ListenSong<T> ( T fish) where T:Fish,Sing
  {
  }
   
   //值类型约束
   public static void ChangeNum<T> (T num) where T:struct
   {
   }
   //引用类型约束
 public static void GetType<T> (T type) where T:class
   {
   }
   //new()无参构造函数约束
 public static void  SetInfo<T> (T type) where T:new()
   {
   }

注意: 多个约束类型的时候, new()必须在最后面。

四、泛型的逆变和协变

1.协变形势

先看下面的代码

     static void Main(string[] args)
     {
         List<Fish> plist = new List<GoldFish>();//编译错误
            plist = new List<Fish>();//编译错误
            plist = new List<GoldFish>();//编译错误
     }

在上面的代码中,plist = new List</Fish/>()、plist = new List</GoldFish/>()两句产生编译错误。虽然Fish是GoldFish的父类,但List</Fish/>类型却不是List</GoldFish/>类型的父类,所以上面的赋值语句报类型转换失败错误。

如果想用子类替换父类怎么办呢?如下,采用IEnumerable 。

  static void Main(string[] args)
   {
       IEnumerable<Fish> fishes = new List<Fish>();
      IEnumerable<Fish> goldfishes = new List<GoldFish>();    
   }

为什么采用IEnumerable就可以呢,查看源码得知如下:

public interface IEnumerable< out T> : IEnumerable

采用了 out 修饰 泛型T后就可以了,这是就是协变。
协变总结起来就是:

1. out 修饰,为逆变逆变是子类转为父类,
2. 逆变只能为返回值。

2.逆变类型

    public interface IMyListIn<in T>
    {
        void Show(T t);
    }
      public class MyList<T> : IMyListIn<T>
    {
        public void Show(T t)
        {
            Console.WriteLine(t.ToString());
        }
    }
   static void Main(string[] args)
   {
      IMyListIn<GoldFish> goldfishes = new MyList<Fish>();
   }

用父类做参数的地方可以用子类替换,这就是逆变。
协变总结起来就是:

1. in修饰,父类向子类的转化
2. 只能做参数输入

五、性能与本质

1.性能对比

下面分别定义三个函数进行 原始类型、obj、和泛型进行对比

 private static void ShowInt(int iParameter)
        {
            //do nothing
        }
        private static void ShowObject(object oParameter)
        {
            //do nothing
        }
        private static void Show<T>(T tParameter)
        {
            //do nothing
        }

在主程序里进行相关的测试:

 public  static void Main(string[] args)
        {
            Console.WriteLine("****************Monitor******************");
            {
                int iValue = 12345;
                long commonSecond = 0;
                long objectSecond = 0;
                long genericSecond = 0;

                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100_000_000; i++)
                    {
                        ShowInt(iValue);
                    }
                    watch.Stop();
                    commonSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100_000_000; i++)
                    {
                        ShowObject(iValue);
                    }
                    watch.Stop();
                    objectSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100_000_000; i++)
                    {
                        Show<int>(iValue);
                    }
                    watch.Stop();
                    genericSecond = watch.ElapsedMilliseconds;
                }
                Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
                    , commonSecond, objectSecond, genericSecond);
            }
        }

测试结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210329142346423.png

2.本质探索

使用 ILSpy查看 可以发现 泛型类在IL中的形势是
.class public auto ansi MyGeneric.Extend.GenericCache`1
extends [mscorlib]System.Object
的形式,说明在编译的时候 泛型的T并没有确定具体的类型,只是在调用的时候才能确定类型。说明泛型并不是简单的语法糖,而是框架进行了升级。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中,可以使用来指定一个类的基类,同时也可以使用约束来限制参数的类。通过的基类和约束,可以实现更加灵活和可复用的代码。 以下是一个示例代码,演示了如何定义一个类,并指定其基类以及约束: ```csharp public class MyBaseClass { public void BaseMethod() { Console.WriteLine("BaseMethod called."); } } public class MyGenericClass<T> : MyBaseClass where T : SomeType { public void GenericMethod(T item) { Console.WriteLine("GenericMethod called."); // 可以访问 MyBaseClass 中的成员 BaseMethod(); // 可以使用 T 类的参数 item 进行操作 Console.WriteLine($"Item: {item}"); } } ``` 在上面的示例中,`MyBaseClass` 是一个基类,`BaseMethod` 是其成员方法。`MyGenericClass<T>` 是一个类,参数 `T` 受到 `SomeType` 约束限制,并且继承自 `MyBaseClass`。在 `MyGenericClass<T>` 中,可以访问 `MyBaseClass` 的成员方法,并且使用 `T` 类的参数进行操作。 使用示例代码: ```csharp MyGenericClass<int> instance = new MyGenericClass<int>(); instance.GenericMethod(42); ``` 在上面的代码中,我们创建了一个 `MyGenericClass<int>` 的实例,并调用了 `GenericMethod` 方法,传入了整数类的参数 `42`。输出结果如下: ``` GenericMethod called. BaseMethod called. Item: 42 ``` 总结起来,通过在类中指定基类和约束,可以使类继承自指定的基类,并对参数进行类约束,提供更加灵活和可复用的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值