[.net 面向对象编程基础] (18) 泛型

上一节我们说到了两种数据类型数组和集合,数组是指包含同一类型的多个元素,集合是指.net中提供数据存储和检索的专用类。

数组使用前需要先指定大小,并且检索不方便。集合检索和声明方便,但是存在类型安全问题,本来使一个类型安全的C#变得不安全了。

集合为了解决数组预设大小的问题,采取了一种自动扩容的办法,这样当大小不够时,他就创建一个新的存储区域,把原有集合的元素复制过来。如此又对性能上也是有很大的影响。

上节我们说到解决这些缺陷的方法,那就是.NET 2.0以后,微软程序猿们推出来的新特性——泛型。

1.什么是泛型?

泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、结构、接口和方法所存储或使用的一个或多个占位符。

这个概念听起来比较绕,其实理解起来也不难,我的理解是类、接口、委托、结构或方法中有类型参数就是泛型类型,这样就有类型参数的概念。泛型集合类可以将类型参数用作它存储对象的点位符;类型参数作为其字段或方法的参数类型出现(这是MSDN中的描述)。

泛型集合所在的命我空间为:System.Collections.Generic

而List类是ArrayList的泛型等效类。该类使用大小按需动态增加的数组实现IList接口。使用方法就是IList<T>和List<T>,这个T就是你要指定的集合的数据或对象类型。

2.泛型声明

泛型类: class Name<t>{}

泛型方法: void Name(T t){}

泛型接口:interface IName<T>{}

泛型结构:struct Name<T>{}

泛型委托:public delegate void Name<T>(T param);

3.泛型方法

泛型我们在定义的时候,说明了他是可以使用占位符来占位类、结构、接口和方法的。我们先看一下方法使用泛型的例子。

我们还是使用前面的例子来看一下使用泛型:

类之间的关系UML图如下:

我们调用假如要实现,让每个动物都叫几声。该如何写呢?

 /// <summary>
/// 动物类(父类 抽象类)
/// </summary>
abstract class Animal
{
/// <summary>
/// 名字
/// 说明:类和子类可访问
/// </summary>
protected string name; /// <summary>
/// 构造函数
/// </summary>
/// <param name="name"></param>
public Animal(string name)
{
this.name = name;
} private int shoutNum = ;
public int ShoutNum
{
get { return shoutNum; }
set { shoutNum = value; }
} /// <summary>
/// 名字(虚属性)
/// </summary>
public virtual string MyName
{
get { return this.name; }
} /// <summary>
/// 叫声,这个方法去掉虚方法,把循环写在这里
/// </summary>
public void Shout()
{
string result = "";
for (int i = ; i < ShoutNum; i++)
result += getShoutSound() + "!"; Console.WriteLine(MyName);
Console.WriteLine(result);
} /// <summary>
/// 创建一个叫声的虚方法,子类重写
/// </summary>
/// <returns></returns>
public virtual string getShoutSound()
{
return "";
} /// <summary>
/// 让所有动集合类的动物叫三次并报名字 (泛型)
/// </summary>
/// <param name="animal"></param>
public static void AnimalShout(IList<Animal> animal)
{
DateTime dt = System.DateTime.Now;
foreach (Animal anm in animal)
{
anm.Shout();
}
Console.WriteLine("使用泛型让所有动物叫一遍所用时间为:" + (System.DateTime.Now - dt).TotalMilliseconds +"毫秒");
}
/// <summary>
/// 让所有动集合类的动物叫三次并报名字 (重载方法 集合)
/// </summary>
/// <param name="animal"></param>
public static void AnimalShout(ArrayList animal)
{
DateTime dt = System.DateTime.Now;
foreach (Animal anm in animal)
{
anm.Shout();
}
Console.WriteLine("使用集合让所有动物叫一遍所用时间为:" + (System.DateTime.Now - dt).TotalMilliseconds + "毫秒");
} /// <summary>
/// 让所有动集合类的动物叫三次并报名字 (重载方法 数组)
/// </summary>
/// <param name="animal"></param>
public static void AnimalShout(Animal[] animal)
{
DateTime dt = System.DateTime.Now;
foreach (Animal anm in animal)
{
anm.Shout();
}
Console.WriteLine("使用数组让所有动物叫一遍所用时间为:" + (System.DateTime.Now - dt).TotalMilliseconds + "毫秒");
}
} /// <summary>
/// 狗(子类)
/// </summary>
class Dog : Animal
{
string myName;
public Dog(string name)
: base(name)
{
myName = name;
} /// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:狗狗,我叫:" + this.name; }
} /// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "汪!";
}
} /// <summary>
/// 狗(子类)
/// </summary>
class ShepherdDog : Dog
{
string myName;
public ShepherdDog(string name)
: base(name)
{
myName = name;
} /// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:牧羊犬,我叫:" + this.name; }
} /// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "汪~呜!";
}
} /// <summary>
/// 猫(子类)
/// </summary>
class Cat : Animal
{
string myName;
public Cat(string name)
: base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:猫咪,我叫:" + this.name; } } /// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "喵!";
}
} /// <summary>
/// 猫(子类)
/// </summary>
class PersianCat : Cat
{
string myName;
public PersianCat(string name)
: base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:波斯猫,我叫:" + this.name; } } /// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "喵~呜!";
}
} /// <summary>
/// 羊(子类)
/// </summary>
class Sheep : Animal
{
string myName;
public Sheep(string name)
: base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:羊羊,我叫:" + this.name; } }
/// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "咩!";
}
}

调用方法:

 //数组
Animal[] animalArray = new Animal[] { new Dog("旺财"), new Cat("小花"), new Cat("阿狸"), new Sheep("纯羊"), new Dog("小白"), new ShepherdDog("汪羊"), new PersianCat("机猫") }; //泛型
IList<Animal> animal = new List<Animal>();
animal.Add(new Dog("旺财"));
animal.Add(new Cat("小花"));
animal.Add(new Cat("阿狸"));
animal.Add(new Sheep("纯羊"));
animal.Add(new Dog("小白"));
animal.Add(new ShepherdDog("汪羊"));
animal.Add(new PersianCat("机猫")); //集合
ArrayList animalArrayList = new ArrayList();
animalArrayList.Add(new Dog("旺财"));
animalArrayList.Add(new Cat("小花"));
animalArrayList.Add(new Cat("阿狸"));
animalArrayList.Add(new Sheep("纯羊"));
animalArrayList.Add(new Dog("小白"));
animalArrayList.Add(new ShepherdDog("汪羊"));
animalArrayList.Add(new PersianCat("机猫")); //调用重载方法看它们的执行叫8次并报名字所需时间
Animal.AnimalShout(animalArray);
Animal.AnimalShout(animal);
Animal.AnimalShout(animalArrayList);
Console.ReadLine();

执行结果如下:

以上的实例并没有模拟出能客观测试效率的环境,因为根据我们的经验数组并不能接受不同类型的元素,而集合和泛型可以,如果使用不同数据测试,也不是客观的。以上实例主要反映了泛型、数组、集合的使用方法,小伙伴们不要太纠结测试时间,不过泛型的时间确实是比较快的。有了泛型小伙伴们就不要再使用ArrayList这个不安全类型了。

数组、List和ArrayList的区别:

上节说了数组和集合ArrayList的区别,这节我们使用了泛型,再说一下他们三者的区别

数组:
(1)在内存中是连续存储的,所以它的索引速度是非常的快,而且赋值与修改元素也很简单。

(2)但是数组也存在一些不足的地方。比如在数组的两个数据间插入数据也是很麻烦的,还有我们在声明数组的时候,必须同时指明数组的长度,数组的长度过长,会造成内存浪费,数组和长度过短,会造成数据溢出的错误。这样如果在声明数组时我们并不清楚数组的长度,就变的很麻烦了.

集合ArrayList:

集合的出现就是为了解决数组的缺陷,但他本身也有缺陷,直到.NET 2.0以后出现泛型,我们可以说这是微软设计上的失误。
(1).ArrayList并非类型安全

ArrayList不论什么类型都接受,实际是接受一个object类型。

比如如下操作:

ArrayList ar = new ArrayList();

ar.Add(111);

ar.Add("bbb");

我们使用foreach遍历的时候   foreach(int array in ar){}那么遇到”bbb”则程度报错,因此我们说他是非安全类型。

(2).遍历ArrayList资源消耗大

因此类型的非安全,我们在使用ArrayList的时候,就意味着增加一个元素,就需要值类型转换为Object对象。遍历的时候,又需要将Object转为值类型。

就是装箱(boxing,指将值类型转换为引用类型)   和
拆箱(unboxing,指将引用类型转换为值类型)

由于装箱了拆箱频繁进行,需要大量计算,因此开销很大。

泛型List:

List和AraayList都是继承于IList,属于等效类,他们之间的区别也是对集合ArrayList局限性的修改

(1)类型安全,通过允许指定泛型类或方法操作的特定类型,泛型功能将类型安全的任务从您转移给了编译器。不需要编写代码来检测数据类型是否正确,因为会在编译时强制使用正确的数据类型。减少了类型强制转换的需要和运行时错误的可能性。

(2)减少开销,泛型提供了类型安全但没有增加多个实现的开销。

3.泛型类

对于泛型类,我们先解决一下实际例子:假如我们有一个泛型类,不知道是什么类型,在初始化的时候再指定类型。类里面有一个方法,可以接受初始化的参数类型,也就是一个泛型方法。

/// <summary>
/// 泛型有一个泛型方法,该方法有一个泛型参数
/// </summary>
/// <typeparam name="T"></typeparam>
class MyClass<T>
{
public static T F(T param)
{
return param;
}
}

调用:

//泛型类调用
int param = , param2= ;
string result1 = (MyClass<string>.F(param.ToString()) + param2).ToString();
string result2 = (MyClass<int>.F(param) + param2).ToString(); Console.WriteLine(result1);
Console.WriteLine(result2);
Console.ReadLine();

可以看到,输出结果为: 23 和 5 ,使用泛型,我们不会因为数据类型不同,就需要去复制一个方法去处理了。

4.类型约束

我们上面定义了泛型,他们默认没有类型约束,就是说可以是任意类型。既然定义了泛型,为何还要约束,其实我们这么理解,泛型首先是一种安全类型,约束他只是让他的范围更小一点而已。

比如某些情况下,我们需要约束类型

主要有以下六种类型的约束:

约束

说明

T:结构

类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。

T:类

类型参数必须是引用类型,包括任何类、接口、委托或数组类型。

T:new()

类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。

T:<基类名>

类型参数必须是指定的基类或派生自指定的基类。

T:<接口名称>

类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

T:U

为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。

类型约束的特点:

A.指定的就是类型参数必须是引用类型,

B.包括任何类、接口、委托或数组类型,如果除了这几样就是非法的了

C.可以输入任何的引用类型同时也确定了范围,防止了输入值类型引发的不安全

D.约束的类型必须是非封闭的类型

E.建议不要对类型参数使用 == 和 != 运算符,因为这些运算符仅测试引用同一性而不测试值相等性

F.如果有new(),则一定要放在最后

未绑定的类型参数

没有约束的类型参数(如公共类 SampleClass<T>{} 中的 T)称为未绑定的类型参数。未绑定的类型参数具有以下规则:

·不能使用 != 和 == 运算符,因为无法保证具体类型参数能支持这些运算符。

·可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。

·可以将它们与 null 进行比较。将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始终返回 false。

裸类型约束

·用作约束的泛型类型参数称为裸类型约束。

·当具有自己的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有用,泛型类的裸类型约束的作用非常有限,因为编译器除了假设某个裸类型约束派生自 System.Object 以外,不会做其他任何假设。

·在希望强制两个类型参数之间的继承关系的情况下,可对泛型类使用裸类型约束。

5.约束举例说明

下面对这几种约束举例说明

还是以这个动物系列为例,下面是实现代码:

 /// <summary>
/// 动物类(父类 抽象类)
/// </summary>
abstract class Animal
{
/// <summary>
/// 名字
/// 说明:类和子类可访问
/// </summary>
protected string name; /// <summary>
/// 构造函数
/// </summary>
/// <param name="name"></param>
public Animal(string name)
{
this.name = name;
} private int shoutNum = ;
public int ShoutNum
{
get { return shoutNum; }
set { shoutNum = value; }
} /// <summary>
/// 名字(虚属性)
/// </summary>
public virtual string MyName
{
get { return this.name; }
} /// <summary>
/// 叫声,这个方法去掉虚方法,把循环写在这里
/// </summary>
public void Shout()
{
string result = "";
for (int i = ; i < ShoutNum; i++)
result += getShoutSound() + "!"; Console.WriteLine(MyName);
Console.WriteLine(result);
} /// <summary>
/// 创建一个叫声的虚方法,子类重写
/// </summary>
/// <returns></returns>
public virtual string getShoutSound()
{
return "";
}
} /// <summary>
/// 声明一个接口 ISpeak(讲话)
/// </summary>
interface ISpeak
{
void Speak();
} /// <summary>
/// 狗(子类)
/// </summary>
class Dog : Animal
{
string myName;
public Dog(string name): base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:狗狗,我叫:" + this.name; }
}
/// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "汪!";
}
} /// <summary>
/// 猫(子类)
/// </summary>
class Cat : Animal
{
string myName;
public Cat(string name): base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:猫咪,我叫:" + this.name; }
}
/// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "喵!";
}
} /// <summary>
/// 蓝猫(子类)
/// 继承 Cat和接口ISpeak
/// </summary>
class BlueCat : Cat,ISpeak
{
string myName;
public BlueCat(string name) : base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:蓝猫,我叫:" + this.name; }
} /// <summary>
/// 实现接口ISpeak的成员Speak
/// </summary>
public void Speak()
{
Console.WriteLine("我会说人话:“你好,我叫:" + this.name + "~~~”");
}
} /// <summary>
/// 羊(子类)
/// </summary>
class Sheep : Animal
{
string myName;
public Sheep(string name): base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:羊羊,我叫:" + this.name; }
}
/// <summary>
/// 叫(重写父类方法)
/// </summary>
public override string getShoutSound()
{
return "咩!";
}
} /// <summary>
/// 喜羊羊(子类)
/// 继承 Sheep和接口ISpeak
/// </summary>
class PleasantSheep : Sheep, ISpeak
{
string myName;
public PleasantSheep(string name) : base(name)
{
myName = name;
}
/// <summary>
/// 名字(重写父类属性)
/// </summary>
public override string MyName
{
get { return "我是:喜羊羊,我叫:" + this.name; }
}
/// <summary>
/// 实现接口ISpeak的成员Speak
/// </summary>
public void Speak()
{
Console.WriteLine("我会说人话:“你好,我叫:" + this.name + "~~~”");
}
}

5.1基类约束

这个比较常见,就是约束类型的实参都必须继承同一基类

我们新建一个泛型类CatShout,约束它的基类实参为Cat类

//泛型约束 - 基类约束
class CatShout<T>
where T : Cat
{
public void Shout(T cat)
{
cat.Shout();
}
}

调用及结果:

//泛型约束- 基类约束
CatShout<Cat> cat = new CatShout<Cat>();
cat.Shout(new BlueCat("兰喵"));
Console.ReadLine();
//CatShout<Dog> dog = new CatShout<Dog>(); 假如使用 Dog类实例化,约束生效,程序报错

5.2 接口约束

这次我们建立一个泛型类AnimalSpeak类,约束它派生自接口ISpeak

//泛型约束 - 接口约束
class AnimalSpeak<T>
where T : ISpeak
{
public void Speak(T t)
{
t.Speak();
}
}

调用及结果:

//泛型约束- 接口约束
AnimalSpeak<BlueCat> animalSpeak = new AnimalSpeak<BlueCat>();
animalSpeak.Speak(new BlueCat("兰喵")); AnimalSpeak<PleasantSheep> animalSpeak2 = new AnimalSpeak<PleasantSheep>();
animalSpeak2.Speak(new PleasantSheep("肥咩")); //假如使用以下调用,约束生效,程序报错,因为Cat并不继承接口ISpeak,不符合接口约束
//AnimalSpeak<Cat> animalSpeak3 = new AnimalSpeak<Cat>();
//animalSpeak2.Speak(new Cat("阿狸"));

说明:可以同时约束为类和接口,类在前面,接口在后。接口可以有多个,类只能一个,不可以继承多个类。

 6.0 要点:

最后总结一下

A.泛型基本的使用比较简单,简单说就是一个取代数组和集合的安全类型

B.泛型包括,泛型类、方法、接口、委托、结构。

C.泛型在某些情况下为了调用更加安全,即在编译阶段就进行校验,经常使用约束

D.泛型的类型约束有几个方面:类约束,接口,构造函数,结构等。基类约束、接口约束和构造函数约束较为常见

E.类的约束使用where关键词,约束的几个方面有先后顺序,通常类,接口,构造函数,这样的顺序。

如果感觉约束这块儿有点难理解,小伙伴们先掌握好基本泛型使用方法。

==============================================================================================

返回目录 <如果对你有帮助,记得点一下推荐哦,有不明白的地方或写的不对的地方,请多交流>

==============================================================================================

[.net 面向对象编程基础] (18) 泛型的更多相关文章

  1. [.net 面向对象编程基础] (1) 开篇

    [.net 面向对象编程基础] (1)开篇 使用.net进行面向对象编程也有好长一段时间了,整天都忙于赶项目,完成项目任务之中.最近偶有闲暇,看了项目组中的同学写的代码,感慨颇深.感觉除了定义个类,就 ...

  2. [.net 面向对象编程基础] (20) LINQ使用

    [.net 面向对象编程基础] (20)  LINQ使用 通过上节LINQ的基础知识的学习,我们可以开始使用LINQ来进行内存数据的查询了,我们上节说了LINQ的定义为:Language Integr ...

  3. Objective-C 基础教程第三章,面向对象编程基础知

    目录 Objective-C 基础教程第三章,面向对象编程基础知 0x00 前言 0x01 间接(indirection) 0x02 面向对象编程中使用间接 面向过程编程 面向对象编程 0x03 OC ...

  4. [.net 面向对象编程基础] (17) 数组与集合

    [.net 面向对象编程基础] (17) 数组与集合 学习了前面的C#三大特性,及接口,抽象类这些相对抽象的东西以后,是不是有点很累的感觉.具体的东西总是容易理解,因此我们在介绍前面抽象概念的时候,总 ...

  5. [.net 面向对象编程基础] (19) LINQ基础

    [.net 面向对象编程基础] (19)  LINQ基础 上两节我们介绍了.net的数组.集合和泛型.我们说到,数组是从以前编程语言延伸过来的一种引用类型,采用事先定义长度分配存储区域的方式.而集合是 ...

  6. [.net 面向对象编程基础] (21) 委托

    [.net 面向对象编程基础] (20)  委托 上节在讲到LINQ的匿名方法中说到了委托,不过比较简单,没了解清楚没关系,这节中会详细说明委托. 1. 什么是委托? 学习委托,我想说,学会了就感觉简 ...

  7. C#面向对象编程基础-喜课堂笔记

    **************[5][C#面向对象编程基础]第1讲:类与对象****************                 *************2.1.1_类与对象的概念**** ...

  8. day23面向对象编程基础

    面向对象编程基础1.面向过程的编程思想    核心过程二字,过程指的是解决问题的步骤,即先干什么\再干什么\后干什么    基于该思想编写程序就好比在设计一条流水线,是一种机械式的思维方式    优点 ...

  9. Python 面向对象编程基础

    Python 面向对象编程基础 虽然Pthon是解释性语言,但是Pthon可以进行面向对象开发,小到 脚本程序,大到3D游戏,Python都可以做到. 一类: 语法: class 类名: 类属性,方法 ...

随机推荐

  1. char、wchar_t、strlen、wcslen

    第一部分: strlen函数的宽字符版是wcslen(wide-character string length:宽字符串长度),并且在STRING.H(其中也说明了strlen)和WCHAR.H中均有 ...

  2. 一条SMS最大字符数,字符数达到多少按MMS处理

    1,一条SMS最大字符数 ----------------------------------------- android\frameworks\opt\telephony中 com.android ...

  3. C# 如何给sql数据库的日期字段插入空值

    在C#中声明日期变量时用SqlDateTime类型,引用:using System.Data.SqlTypes; 例子:user.AbortDate = SqlDateTime.Null;

  4. iOS常用公共方法

      iOS常用公共方法 字数2917 阅读3070 评论45 喜欢236 1. 获取磁盘总空间大小 //磁盘总空间 + (CGFloat)diskOfAllSizeMBytes{ CGFloat si ...

  5. hdoj 2022 海选女主角

    Problem Description potato老师虽然很喜欢教书,但是迫于生活压力,不得不想办法在业余时间挣点外快以养家糊口.“做什么比较挣钱呢?筛沙子没力气,看大门又不够帅...”potato ...

  6. ASP.NET操作ORACLE数据库之模糊查询

    ASP.NET操作ORACLE数据库之模糊查询 一.ASP.NET MVC利用OracleHelper辅助类操作ORACLE数据库 //连接Oracle数据库的连接字符串 string connect ...

  7. IP地址框

    //IP地址框 // 此段代码:独立的获取本机IP地址和计算机名 WORD wVersionRequested; WSADATA wsaData; char name[255]; CString ip ...

  8. iOS 应用中有页面加载gif动画,从后台进入前台时就消失了

    解决办法: 在Appdelegate.m 里面有一个从后台进入前台所响应的方法,可以在该方法里post 一个通知,在加载动画里的页面接受通知,响应一定的方法即可 #pragma -mark 当程序进入 ...

  9. JAVA里面的IO流(二)方法1、输入流

  10. Build Android Webrtc Libjingle Library On Ubuntu

    Our team is developing an app to help people solve problem face to face. We choose webrtc protocol a ...