• C#学习笔记(五):泛型


    认识泛型

    泛型使类型参数化,从而实现了算法上的代码重用。

    同时由于去掉了转换中装箱和拆箱的操作,使用泛型还可以提高程序的运行速度。

    我们先看看C#自带的使用了泛型的类:

     1 using System.Collections.Generic;
     2 
     3 namespace Study
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             List<int> list1 = new List<int>();
    10             list1.Add(100);
    11             int i = list1[0];
    12 
    13             List<string> list2 = new List<string>();
    14             list2.Add("Hello");
    15             string s = list2[0];
    16         }
    17     }
    18 }

    通过使用泛型,我们可以重复利用List提供的功能,而不用每个类型对应去写一个List的类。

    泛型在类上的实现

    下面我们自己使用泛型编写一个简单的类,如下:

     1 using System;
     2 
     3 namespace Study
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             Test<int> test1 = new Test<int>();
    10             test1.myValue = 100;
    11             Console.WriteLine(test1.myValue);
    12 
    13             Test<string> test2 = new Test<string>();
    14             test2.myValue = "Hello";
    15             Console.WriteLine(test2.myValue);
    16         }
    17     }
    18 
    19     public class Test<T>
    20     {
    21         private T _myValue;
    22 
    23         public T myValue
    24         {
    25             set { _myValue = value; }
    26             get { return _myValue; }
    27         }
    28     }
    29 }

    Test类中的尖括号里面的T即为泛型,其可以表示任意的类型。

    泛型约束

    我们上面示例中的T可以使用任意的类型,那么如果我们只希望T是某类型或某类型的子类该怎么办呢?

    public class Test<T> where T : IComparable

    如果这样写,则表示T必须是实现了IComparable接口的对象。

    多个类型的情况

    多个类型的写法如下:

    public class Test<T, K> where T : IComparable where K : ICloneable

    如上所示,一个类型如果要添加约束就需要写一个where进行对应,所以有多个就会有多个where关键字出现。

    创建类型的情况

    如果需要使用new创建一个类型,则需要在约束里添加new()的字符串,如下:

     1 using System;
     2 
     3 namespace Study
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             Test<Data> test1 = new Test<Data>();
    10             Console.WriteLine(test1.myComparable.s);
    11 
    12             Console.Read();
    13         }
    14     }
    15 
    16     public class Test<T> where T : IComparable, new()
    17     {
    18         private T _myComparable;
    19 
    20         public T myComparable
    21         {
    22             set { _myComparable = value; }
    23             get { return _myComparable; }
    24         }
    25 
    26         public Test()
    27         {
    28             _myComparable = new T();
    29         }
    30     }
    31 
    32     public class Data : IComparable
    33     {
    34         public string s = "Hello World!";
    35 
    36         public int CompareTo(object obj)
    37         {
    38             return 0;
    39         }
    40     }
    41 }

    但是如果是值类型,则不需要这么写,但是要约束T为值类型,如下:

     1 using System;
     2 
     3 namespace Study
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             Test<int> test1 = new Test<int>();
    10             Console.WriteLine(test1.myComparable);
    11 
    12             Console.Read();
    13         }
    14     }
    15 
    16     public class Test<T> where T : struct
    17     {
    18         private T _myComparable;
    19 
    20         public T myComparable
    21         {
    22             set { _myComparable = value; }
    23             get { return _myComparable; }
    24         }
    25 
    26         public Test()
    27         {
    28             _myComparable = new T();
    29         }
    30     }
    31 }

    default关键字

    当我们需要对泛型T置空时不能直接写“xxx=null;”因为只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。所以我们使用default关键字就可以解决这个问题,如下:

    _myComparable = default(T);

    泛型继承

    子类也有相同的泛型时:

    1 public class A<T>
    2 { }
    3 
    4 public class B<T> : A<T>
    5 { }

    当然,你可以使用另外的名称,只要能对应上即可:

    1 public class A<T>
    2 { }
    3 
    4 public class B<K> : A<K>
    5 { }

    子类指定好类型:

    1 public class A<T>
    2 { }
    3 
    4 public class B : A<string>
    5 { }

    子类添加新类型:

    1 public class A<T>
    2 { }
    3 
    4 public class B<T, K> : A<T>
    5 { }

    泛型在方法上的实现

    如果要在方法上添加类上没有指定的类型,可以直接在方法上添加泛型:

     1 using System;
     2 
     3 namespace Study
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             Test<int> test = new Test<int>();
    10             Console.WriteLine(test.Func<string>("Hello"));
    11 
    12             Console.Read();
    13         }
    14     }
    15 
    16     public class Test<T>
    17     {
    18         public K Func<K>(K k)
    19         {
    20             return k;
    21         }
    22     }
    23 }

    泛型在委托上的实现

    委托上也可以使用泛型,定义方法和在方法上使用泛型一致,如下:

     1 using System;
     2 
     3 namespace Study
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             new Test();
    10 
    11             Console.Read();
    12         }
    13     }
    14 
    15     public class Test
    16     {
    17         public delegate T add<T>(T a, T b);
    18 
    19         public Test()
    20         {
    21             add<int> func1 = AddInt;
    22             Console.WriteLine(func1(100, 23));
    23 
    24             add<float> func2 = AddFloat;
    25             Console.WriteLine(func2(1.2f, 0.03f));
    26         }
    27 
    28         private int AddInt(int a, int b)
    29         {
    30             return a + b;
    31         }
    32 
    33         private float AddFloat(float a, float b)
    34         {
    35             return a + b;
    36         }
    37     }
    38 }

    泛型接口

    泛型接口的使用和泛型类一致,大家可以查看微软自己的文档:https://msdn.microsoft.com/zh-cn/library/kwtft8ak(VS.80).aspx

    泛型和静态字段与方法

    泛型同样可以使用在静态字段和方法中,由于静态字段和方法在内存中始终只存在一个,所以当我们使用了泛型的时候,编译器会帮我们自动生成对应的方法。

    泛型静态的使用和动态一致就跳过不说了。

    类型推断

    我们在调用泛型方法时可以省略泛型类型的书写,完全交由编译器根据我们的类型来进行判断,这样可以减小代码量同时也更清晰:

     1 using System;
     2 
     3 namespace Study
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             //没有类型推断
    10             Console.WriteLine(CompareTo<int>(100, 100));
    11 
    12             //使用类型推断
    13             Console.WriteLine(CompareTo(12.3f, 12.33f));
    14             Console.WriteLine(CompareTo('a', 'a'));
    15 
    16             Console.Read();
    17         }
    18 
    19         private static int CompareTo<T>(T a, T b) where T : IComparable
    20         {
    21             return a.CompareTo(b);
    22         }
    23     }
    24 }

    泛型的可变性

    我们先看一个例子:

     1 using System;
     2 using System.Collections.Generic;
     3 
     4 namespace Study
     5 {
     6     class Program
     7     {
     8         static void Main(string[] args)
     9         {
    10             List<B> listB = new List<B>();
    11 
    12             //下面这句代码会报错
    13             //Cannot convert source type 'System.Collections.Generic.List<Study.B>' to 
    14             //target type 'System.Collections.Generic.List<Study.A>'
    15             List<A> listA = listB;
    16 
    17             Console.Read();
    18         }
    19     }
    20 
    21     public class A
    22     {}
    23 
    24     public class B : A
    25     {}
    26 }

    我们发现虽然B继承于A,但是List<A>和List<B>之间是不能相互转换的。

    为了解决这个问题,微软在C#4.0中添加了对泛型的可变性的支持。

    协变性

    协变性指的是泛型类型参数可以从一个派生类隐式地转换为其基类。

    out关键字

    协变使用out关键字标识,如下:

    public interface MyInterface<out T>

    示例:

     1 using System;
     2 using System.Collections.Generic;
     3 
     4 namespace Study
     5 {
     6     class Program
     7     {
     8         static void Main(string[] args)
     9         {
    10             MyInterface<B> myB = new MyClass<B>();
    11             MyInterface<A> myA = myB;
    12 
    13             Console.Read();
    14         }
    15     }
    16 
    17     public interface MyInterface<out T>
    18     {}
    19 
    20     public class MyClass<T> : MyInterface<T>
    21     {}
    22 
    23     public class A
    24     {}
    25 
    26     public class B : A
    27     {}
    28 }

    逆变性

    逆变性指的是泛型类型参数可以从一个基类隐式的转换为其派生类。

    in关键字

    public interface MyInterface<in T>

    示例:

     1 using System;
     2 using System.Collections.Generic;
     3 
     4 namespace Study
     5 {
     6     class Program
     7     {
     8         static void Main(string[] args)
     9         {
    10             MyInterface<A> myA = new MyClass<A>();
    11             MyInterface<B> myB = myA;
    12 
    13             Console.Read();
    14         }
    15     }
    16 
    17     public interface MyInterface<in T>
    18     {}
    19 
    20     public class MyClass<T> : MyInterface<T>
    21     {}
    22 
    23     public class A
    24     {}
    25 
    26     public class B : A
    27     {}
    28 }

    注意事项

    1. 只有接口和委托支持协变和逆变,类或方法都不支持协变和逆变;
    2. 协变和逆变只支持引用类型,值类型不支持协变和逆变;
    3. 必须显示的使用out或in来标记协变和逆变;
    4. 委托的协变和逆变不要在多播委托中使用;
    5. 协变和逆变不能同时使用,只能选择一种;
  • 相关阅读:
    Java设计模式菜鸟系列(四)工厂方法模式建模与实现
    决策树分类
    SVD神秘值分解
    省市区三级联动菜单(附数据库)
    POJ 3076 Sudoku (dancing links)
    HDOJ 4862 Jump
    BEGINNING SHAREPOINT&#174; 2013 DEVELOPMENT 第3章节--SharePoint 2013 开发者工具 站点设置
    Transparency Tutorial with C#
    【剑指offer】不用加减乘除做加法
    POJ2112_Optimal Milking(网洛流最大流Dinic+最短路Flody+二分)
  • 原文地址:https://www.cnblogs.com/hammerc/p/4608349.html
Copyright © 2020-2023  润新知