1.定义泛型类

可以使用以下语法创建泛型类,T可以是任意符合C#标识符命名规范的任意标识符

class MyGenericClass<T>
{
//....
}

泛型类可以包含任意多个类型,使用逗号隔开。定义了这些类型之后就可以像其他类型一样使用它们,比如用作成员变量的类型,属性或方法的返回值,方法的参数类型等等。如下把T1用作成员变量的类型、属性的返回值,方法的参数类型。

    class MyGenericClass<T1, T2, T3>
{
private T1 innerT1Object; public MyGenericClass()
{ } public MyGenericClass(T1 t)
{
innerT1Object = t;
} public T1 InnerT1Object
{
get { return innerT1Object; }
} }

注:不能使用定义的类型T来创建它的对象,因为T可能是抽象类,或者没有公共的构造函数。我们唯一可以假设的是T是继承于System.Object的类型,只能使用Object类提供的方法。

如下代码是不能编译的

    class MyGenericClass<T1, T2, T3>
{
private T1 innerT1Object; public MyGenericClass()
{
innerT1Object = new T1();
} public MyGenericClass(T1 t)
{
innerT1Object = t;
} public T1 InnerT1Object
{
get { return innerT1Object; }
} }

也不能用+、-、==、!=等运算符比较两个T对象,因为该对象可能不支持这个运算符,但可以用==和!==比较T对象和null

        public bool Compare(T1 op1,T2 op2)
{
if (op1 != null && op2 != null)//正确
return true;
else
return false;
} public bool Compare(T1 op1, T2 op2)
{
if (op1 == op2)//错误
return true;
else
return false;
}

1.1Default关键字

因为不知道T类型是什么类型,所以就不好为T类型的变量赋值,但可以使用default为T类型的变量赋值,它可以根据T的具体类型赋予默认值,比如引用类型赋值null,int赋予0

        public MyGenericClass()
{
innerT1Object = default(T1);
}

1.2约束

前面定义的泛型类型称为无绑定类型,因为没有对它们进行任何约束。可以使用约束把T类型限制为某个类或者继承于某个具体的类。

使用where 关键字约束泛型,可以有多个约束,多个约束直接用逗号隔开,也可以有多个where用于约束不同的类型,where约束必须写在继承的类或接口后面

class MyGenericClass<T1,T2>:IMyInterface where T1:Constraint1,Constraint2 where T2:Constraint3

下表列出了一些可用的约束

约束 定义
struct T必须是值类型,比如int,结构等
class T必须是引用类型,比如string、类
base-class T是某个类型或者继承于某个类型,使用时用具体的类名替代,比如T:Animal
interface T是某个接口或者继承了某个接口,如T:IMyInterface
new() T必须有无参数的公共构造函数,如果new()用做约束,它必须是为类型T指定的最后一个约束

如下例子示范了struct、class、base-class约束的使用。因为T1约束为值类型,所以定义泛型变量时T1只能是int等值类型,T2约束为引用类型,所以第二个参数类型必须是引用类型,T3使用base-class约束把T3约束为Animal或者继承于Animal的类。

class MyGenericClass<T1, T2, T3> where T1:struct where T2:class where T3:Animal
{
} MyGenericClass<int, string, Cow> generic = new MyGenericClass<int, string, Cow>();

可用通过base-class约束把一个类型用做另一个类型的约束,这称为裸类型约束,表示两个参数类型是同一个类型,或者继承于另一个类型

 class MyGenericClass<T1, T2, T3> where T1:T2
{
}

但是约束不能循环,如下定义是不能编译的

    class MyGenericClass<T1, T2, T3> where T1:T2 where T2:T1
{
}

约束可以是接口类型,表示T是该接口类型或者实现了这个这个接口,如果使用了new()约束,new()约束要放在每个where的最后

    class MyGenericClass<T1, T2, T3> where T1:MyInterface ,new() where T2:class
{
}

创建泛型类实例

abstract class Animal
{
protected string name;
public string Name
{
get { return name; }
set { name = value; }
} public Animal()
{
name = "The animal has no name.";
} public Animal(string newName)
{
name = newName;
} public void Feed()
{
Console.WriteLine("{0}has been fed.", name);
} public abstract void MakeANoise(); } class Cow : Animal
{
public void Milk()
{
Console.WriteLine("{0}has been milked.", name);
} public Cow(string newName)
: base(newName)
{ } public override void MakeANoise()
{
Console.WriteLine("{0} say 'moo'",name);
}
} class Chicken : Animal {
public void LagEgg()
{
Console.WriteLine("{0}has lag en egg.", name);
} public Chicken(string newName)
: base(newName)
{ } public override void MakeANoise()
{
Console.WriteLine("{0} say 'Cluck'", name);
}
} class SuperCow : Cow
{
public SuperCow(string name)
: base(name)
{ } public void Fly()
{
Console.WriteLine("{0} is flying", name);
} public override void MakeANoise()
{
Console.WriteLine("{0} say 'here i come to save the world'", name);
}
} class Farm<T>:IEnumerable<T> where T : Animal
{
private List<T> animals = new List<T>();
public List<T> Animails
{
get { return animals; }
} public void MakeNoises()
{
foreach (T animal in animals)
{
animal.MakeANoise();
}
} public void FeedTheAnimals()
{
foreach (T animal in animals)
{
animal.Feed();
}
} public Farm<Cow> GetCows()
{
Farm<Cow> cows = new Farm<Cow>();
foreach (T animal in animals)
{
if (animal is Cow)
cows.Animails.Add(animal as Cow);
} return cows;
} //实现IEnumerable<T>接口用于迭代Farm<T>集合,实现该接口,需要实现下面两个方法
public IEnumerator<T> GetEnumerator()
{
return animals.GetEnumerator();
} IEnumerator IEnumerable.GetEnumerator()
{
return animals.GetEnumerator();
}
} class Program
{ static void Main(string[] args)
{
Farm<Animal> farm = new Farm<Animal>();
farm.Animails.Add(new Cow("Jack"));
farm.Animails.Add(new Chicken("Vera"));
farm.Animails.Add(new Chicken("Sally"));
farm.Animails.Add(new SuperCow("Kavin"));
farm.MakeNoises(); Farm<Cow> cowFarm = farm.GetCows();
foreach (Cow cow in cowFarm)
{
if (cow is SuperCow)
(cow as SuperCow).Fly(); } Console.ReadLine();
}
}

该实例通过在内部利用List<T>泛型类实现自己的泛型类型,并实现IEnumerable<T>接口迭代集合。

注:上面的泛型类继承了IEnumerable<T>,在Farm<T>提供的约束也会应用在IEnumerable<T>上。如果在基类上也定义了约束,则子类不能解除约束,也就是说类型T在子类必须受至少到与基类相同的约束。

子类不能解除约束的意思是:如果子类没有约束,必须要在子类的定义中显式把父类的约束写出来。假如有一个子类SuperFarm<T>继承了Fram<T>类,它没有自己的约束,因为基类Farm<T>约束了T必须为Animal,所以子类定义时必须把这个约束显式写出

    //正确
class SuperFarm<T> : Farm<T> where T:Animal
{ }
//错误
class SuperFarm<T> : Farm<T>
{ }

如果子类有自己的约束,则子类的约束必须是父类的约束的一个子集,或者说子类约束中的对象必须能隐式转换为父类约束中的对象。如下约束T为Cow,Cow是Animal的一个子类,可以隐式转换为Animal。

    class SuperFarm<T> : Farm<T> where T:Cow
{ }

如果一个类继承了泛型类,且改类不是泛型类,则它必须明确给出所有必须的类型信息,比如

    //正确
class SuperFarm : Farm<Cow>
{ } //错误的,没有给出类型T的具体类型信息
class SuperFarm : Farm<T>
{ }

1.3泛型运算符

与其他类一样,泛型类可以对运算符进行重载。例如我们可以在Fram<T>中重载如下运算符,这样我们就能计算Farm<Animal> newFarm=farm+cowFarm这里的类型,其中cowFarm是Farm<Cow>的实例,farm是Farm<Animal>的实例,使用隐式转换运算符,我们就能把cowFarm隐式转换为List<Animal>类型,进而利用第二个运算符进行计算。

        public static implicit operator List<Animal>(Farm<T> farm)
{
List<Animal> result = new List<Animal>();
foreach (T animal in farm)
{
result.Add(animal);
} return result;
} public static Farm<T> operator +(Farm<T> farm1, List<T> farm2)
{
Farm<T> result=new Farm<T> (); foreach(T animal in farm1)
{
result.Animails.Add(animal);
} foreach(T animal in farm2)
{
if(!result.Contains(animal))
result.animals.Add(animal);
} return result;
}

第二个运算符中,第一个参数必须为Farm<T>类型,第二个参数必须为List<T>类型,为了使Farm<Animal> newFarm=cowFarm+farm能计算,再添加一个运算符,这个运算符利用了现有的运算符。

        public static Farm<T> operator +(List<T> farm1, Farm<T> farm2)
{
return farm2 + farm1;
}

有人说利用下面这个运算符也能实现Farm<Animal> newFarm=cowFarm+farm的运算

        public static Farm<T> operator +(Farm<T> farm1, Farm<T> farm2)
{
Farm<T> result = new Farm<T>(); foreach (T animal in farm1)
{
result.Animails.Add(animal);
} foreach (T animal in farm2)
{
if (!result.Contains(animal))
result.animals.Add(animal);
} return result;
}

事实上这是不行的,因为Farm<Animal>和Farm<Cow>是不同的类型,他们不能进行计算。要使上面的式子能计算,必须重载隐式转换运算符,把Farm<Cow>转换为Farm<Animal>

        public static implicit operator Farm<Animal>(Farm<T> farm)
{
Farm<Animal> result = new Farm<Animal>();
foreach (T animal in farm)
{
result.animals.Add(animal);
} return result;
}

从上面的例子可以看出,泛型中,如果类型T是继承的关系,一般都把子类转换为最基本的父类然后进行运算。

2.泛型结构

可以用与类类似的方式,定义泛型结构,只不过结构是值类型。

    public struct MyStruct<T1, T2>
{
public T1 item1;
public T2 item2;
}

 3.泛型接口

泛型接口的定义和类一样,下面的泛型接口中T用作方法AttemptToBreed的参数类型和属性OldestInHerd的返回类型。

    interface MyInterface<T> where T : Animal
{
bool AttemptToBreed(T animal1, T animal2);
T OldestInHerd { get; }
}

4.泛型方法

之前的例子中有一个GetCow方法,假如我们需要再获取Chicken的集合,是不是需要再写一个方法,这样太麻烦了,泛型方法可以很好的解决这个问题。

        public Farm<Cow> GetCows()
{
Farm<Cow> cows = new Farm<Cow>();
foreach (T animal in animals)
{
if (animal is Cow)
cows.Animails.Add(animal as Cow);
} return cows;
}

泛型方法的标准是方法声明中有个尖括号,中间是类型符T,比如我们可以添加一个泛型方法解决刚刚的问题。

        public Farm<U> GetSpecies<U>() where U:T
{
Farm<U> result = new Farm<U>();
foreach (T animal in animals)
{
if (animal is U)
result.animals.Add(animal as U);
} return result;
}

注:泛型方法的类型参数不能用和Farm<T>相同的类型参数标识符,即Farm<T>用T做类型参数标识符,泛型方法就不能用T,因为它们表示的含义不一样。个人理解是Farm<T>是该类能处理的类型,GetSpecies<U>中的U是泛型方法能处理的类型,T和U可能没有半点联系。如可以在Farm<T>类中定义,如下方法,该泛型方法的U和类中的T没任何关系

        public T GetDefault<U>()
{
return default(T);
}

也可以在非泛型类中定义泛型方法,泛型方法可以和泛型类一样定义约束

    class MyDefault
{
public static T GetDefault<T>() where T:Animal
{
return default(T);
}
}

5.泛型委托

我们知道声明委托时,我们只要声明一个和方法签名、方法返回值一致的委托,就可以在程序中像声明对象一样使用这个委托,并把方法作为参数传递给委托变量,如下

    class Program
{
delegate int MyDelegate(int op1, int op2);
static void Main(string[] args)
{
MyDelegate sum = new MyDelegate(Add);
MyDelegate Multiply = new MyDelegate(Mul); Console.WriteLine(sum(, ));//
Console.WriteLine(Multiply(, ));//
Console.ReadLine();
} public static int Add(int op1, int op2)
{
return op1 + op2;
} public static int Mul(int op1, int op2)
{
return op1 * op2;
}
}

声明泛型委托只需要使用一个或几个泛型参数。泛型委托和一般委托相比,处理的数据类型更大了。

    class Program
{
delegate T MyGenericDelegate<T>(T op1, T op2);
static void Main(string[] args)
{
MyGenericDelegate<int> sum2 = new MyGenericDelegate<int>(Add);
MyGenericDelegate<int> Multiply2 = new MyGenericDelegate<int>(Mul);
Console.WriteLine(sum(, ));//
Console.WriteLine(Multiply(, ));//
Console.ReadLine();
} public static int Add(int op1, int op2)
{
return op1 + op2;
} public static int Mul(int op1, int op2)
{
return op1 * op2;
}
}

 6.变体

协变和抗变(逆变)统称变体。

我们知道使用 多态性可以把子类的对象放在基类的变量中,比如

Cow myCow=new Cow("Cow");
Animal myAnimal =myCow;

那么对于泛型,像下面这样的代码能不能执行呢?

List<Cow> cows=new List<Cow>();
cows.Add(new Cow("Cow1"));
List<Animal> animals = cows;

答案是不能的,因为虽然Cow和Animal有继承关系, 但在泛型上类型List<Cow>和List<Animal>是两个不同的类型,它们不具有继承关系,不能互相转换。为了使上面的代码能运行,C#引入了另一个概念,协变。

在泛型中,在类型参数T前面使用out关键字就可以定义协变,协变只能在接口和泛型委托中使用,用作方法或者get块的返回值。C#中使用协变的一个常用接口是IEnumerable<T>

    // 摘要:
// 公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代。
//
// 类型参数:
// T:
// 要枚举的对象的类型。
[TypeDependency("System.SZArrayHelper")]
public interface IEnumerable<out T> : IEnumerable
{
// 摘要:
// 返回一个循环访问集合的枚举器。
//
// 返回结果:
// 可用于循环访问集合的 System.Collections.Generic.IEnumerator<T>。
IEnumerator<T> GetEnumerator();
}

因为IEnumerable<T>定义了协变,所以以下代码是正确的。List<T>实现了IEnumerable<T>,利用多态性可以把List<Cow>对象存在IEnumerable<Cow>变量中,利用协变可以把IEnumerable<Cow>对象放在IEnumerable<Animal>变量中。

            List<Cow> cows = new List<Cow>();
cows.Add(new Cow("Cow1"));
IEnumerable<Cow> iCows = cows;//多态性
IEnumerable<Animal> iAnimals = iCows;//协变

也可以在委托中定义协变

delegate T MyGenericDelegate2<out T>();

抗变:抗变和协变相反,抗变可以把泛型对象值存放在T类型的派生子类的泛型变量中。通过在类型变量T前面使用关键字in定义抗变,抗变只能用在接口和泛型委托中,用作方法参数。 比如IComparer<T>

    // 摘要:
// 定义类型为比较两个对象而实现的方法。
//
// 类型参数:
// T:
// 要比较的对象的类型。
public interface IComparer<in T>
{
// 摘要:
// 比较两个对象并返回一个值,指示一个对象是小于、等于还是大于另一个对象。
//
// 参数:
// x:
// 要比较的第一个对象。
//
// y:
// 要比较的第二个对象。
//
// 返回结果:
// 一个带符号整数,它指示 x 与 y 的相对值,如下表所示。值含义小于零x 小于 y。零x 等于 y。大于零x 大于 y。
int Compare(T x, T y);
}

上面的接口定义了抗变,所以我们可以排序Cows把IComparer<Cow> 对象传给 IComparer<Animal>类型的变量,从而实现排序

    class AnimalComparer : IComparer<Animal>
{
public static AnimalComparer Default = new AnimalComparer(); public int Compare(Animal x, Animal y)
{
return x.Name.Length.CompareTo(y.Name.Length) ;
}
} List<Cow> cows = new List<Cow>();
cows.Add(new Cow("Cow22"));
cows.Add(new Cow("Cow1"));
cows.Add(new Cow("Cow111"));
cows.Sort(AnimalComparer.Default);

7.习题

创建一个泛型类ShortCollection<T>,它实现了IList<T>接口,包含一个集合及集合的最大容量。这个最大容量可以通过构造函数设置,或者默认为10.构造函数还可以通过List<T>参数获取项的最初列表。该类与Collection<T>功能相同,但是如果试图添加超过最大容量的项,它会抛出IndexOutOfRangeException异常。

class ShortCollection<T>:IList<T>
{
protected int maxSize = ;
protected Collection<T> innerCollection; public ShortCollection():this()
{ } public ShortCollection(int size)
{
maxSize = size;
innerCollection = new Collection<T>();
} public ShortCollection(List<T> list):this(,list)
{ } public ShortCollection( int size,List<T> list)
{
maxSize =size ;
if (list.Count < maxSize)
{
innerCollection = new Collection<T>(list);
}
else
{
ThrowTooManyItemException();
}
} protected void ThrowTooManyItemException()
{
throw new IndexOutOfRangeException("Unable to add any more items,maxinum size is " + maxSize + " items.");
}
public int IndexOf(T item)
{
return innerCollection.IndexOf(item);
} public void Insert(int index, T item)
{
if (innerCollection.Count < maxSize)
innerCollection.Insert(index, item);
else
ThrowTooManyItemException();
} public void RemoveAt(int index)
{
innerCollection.RemoveAt(index);
} public T this[int index]
{
get
{
return innerCollection[index];
}
set
{
innerCollection[index]=value;
}
} public void Add(T item)
{
if (innerCollection.Count < maxSize)
innerCollection.Add(item);
else
ThrowTooManyItemException();
} public void Clear()
{
innerCollection.Clear();
} public bool Contains(T item)
{
return innerCollection.Contains(item);
} public void CopyTo(T[] array, int arrayIndex)
{
innerCollection.CopyTo(array, arrayIndex);
} public int Count
{
get { return innerCollection.Count; }
} public bool IsReadOnly
{
get { return (innerCollection as IList<T>).IsReadOnly; }
} public bool Remove(T item)
{
return innerCollection.Remove(item);
} public IEnumerator<T> GetEnumerator()
{
return innerCollection.GetEnumerator();
} System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return innerCollection.GetEnumerator();
}
}

20.C# 创建自己的泛型类型的更多相关文章

  1. PHP基础20:创建文件

    <?php /* 1.PHP 创建文件 - fopen() fopen() 函数也用于创建文件.也许有点混乱,但是在 PHP 中,创建文件所用的函数与打开文件的相同 如果您用 fopen() 打 ...

  2. ROS2学习之旅(20)——创建一个动作消息

    本文用来自定义一个动作消息类型. 以下命令用来创建一个工作空间并建立一个功能包: mkdir -p action_ws/src cd action_ws/src ros2 pkg create act ...

  3. Oracle SQLserver数据库创建表ID字段的自动递增_序列

    Oracle 将表t_uaer的字段ID设置为自增:(用序列sequence的方法来实现) ----创建表 Create table t_user( Id ),userid ),loginpasswo ...

  4. 【java开发系列】— JDOM创建、改动、删除、读取XML文件

    有非常多中操作XML文件的方法,这里介绍一下JDOM的用法和技巧. JDOM下载地址 创建XML文档 XML文件是一种典型的树形文件,每一个文档元素都是一个document元素的子节点. 而每一个子元 ...

  5. Oracle02——oracle分页、子查询、集合运算、处理数据、创建和管理表和其他数据库对象

    作者: kent鹏 转载请注明出处: http://www.cnblogs.com/xieyupeng/p/7289451.html --oracle分页(Pageing Query) select ...

  6. Java 线程的创建和启动

    Java 使用 Thread 类代表线程,所有的线程对象都必须是 Thread 类或其子类的实例.每个线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码). Java 使用线程执 ...

  7. blfs(systemv版本)学习笔记-使用apache创建简单的网页服务器

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! apache项目地址:http://www.linuxfromscratch.org/blfs/view/stable/serv ...

  8. github使用指南(2015年3月23日更新了本地创建仓库再推送到remote仓库的使用方法)

    我是通过这个来学习的.个人愚笨,琢磨了半天,终于搞通了,醉了醉了,以前一直使用svn,用git确实有点水土不服.本文以如何使用git为主来展开,不涉及太多理论. git是分布式的版本管理.什么叫分布式 ...

  9. 约束Constraints--主键约束、外键约束、唯一约束、检查约束、默认约束、NOT NULL约束、列约束与表约束、创建约束、删除约束

    约束   Including Constraints 以下内容转自:https://www.cnblogs.com/wcl2017/p/7043939.html和http://blog.csdn.ne ...

随机推荐

  1. echarts的地图点击事件

    1.echarts的地图展示,有时候展示出的数据,虽然鼠标点击上去某个省份或者某个地方会有数据显示,但是点击一下地图没有任何动态效果,如何添加地图点击效果呢,这里自己也是坐下笔记,方便以后使用. 参考 ...

  2. Kafka(二)CentOS7.5搭建Kafka2.11-1.1.0集群与简单测试

    一.下载 下载地址: http://kafka.apache.org/downloads.html    我这里下载的是Scala 2.11对应的 kafka_2.11-1.1.0.tgz 二.kaf ...

  3. Spark环境搭建(四)-----------数据仓库Hive环境搭建

    Hive产生背景 1)MapReduce的编程不便,需通过Java语言等编写程序 2) HDFS上的文缺失Schema(在数据库中的表名列名等),方便开发者通过SQL的方式处理结构化的数据,而不需要J ...

  4. css 定位布局

    文档流: 文档流,是指盒子按照html标签编写的顺序依次从上到下,从左到右排列.块元素占一行,行内元素在一行之内从左到在排列,先写的先排列,后写的排在后面,每个盒子都占据自己的位置. 关于定位: 可以 ...

  5. react-native添加react-native-vector-icons插件android遇到的问题

    问题 yarn add react-native-vector-icons后图省事使用react-native link来添加native配置,结果run时报错. ps:安装的需要native的插件不 ...

  6. ko.js学习一

    一.KO是一个MVVM框架 声明式绑定 (Declarative Bindings):使用简明易读的语法很容易地将模型(model)数据关联到DOM元素上. UI界面自动刷新 (Automatic U ...

  7. javascript对象序列化(对象与JSON字符串的互换)

    前一段时间用到h5的本地存储---需要把某个js对象存储在浏览器的本地存储中,用到了javascript对象的序列化与反序列化 所谓的序列化就是将对象的状态信息转换为可以存储或传输的形式的过程,基本上 ...

  8. Python 面试中可能会被问到的30个问题

    第一家公司问的题目 1 简述解释型和编译型编程语言? 解释型语言编写的程序不需要编译,在执行的时候,专门有一个解释器能够将VB语言翻译成机器语言,每个语句都是执行的时候才翻译.这样解释型语言每执行一次 ...

  9. Robot Framework 1

    about Robot, learnt from the following document, perfect document !!!! http://www.virtuousprogrammer ...

  10. Linux系统的命令应该如何记?

    Linux入门篇: 很多刚入门的同学,就像无头的苍蝇一样,到处找视频.书籍.网站帖子之类的学习方式,视频虽然讲得详细,但是时间的投入也是巨大的,播放时间,练习时间,加起来很吓人,其实啊很少有人能坚持把 ...