net 自定义泛型那点事
泛型概述
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。
泛型定义
正文
假如我想输出double ,int 和 dateTime类型的类型名字和他们的值。现在需要定义方法如下
using System; using System.Data; namespace testData { class Program { static void Main(string[] args) { ShowInt(2); ShowDateTime(DateTime.Now); ShowDouble(2.52); Console.Read(); } public static void ShowInt(int value) { Console.WriteLine($"typeName:{value.GetType().Name},Value:{value}"); } public static void ShowDouble(double value) { Console.WriteLine($"typeName:{value.GetType().Name},Value:{value}"); } public static void ShowDateTime(DateTime value) { Console.WriteLine($"typeName:{value.GetType().Name},Value:{value}"); } } }
这样写起来,好像比较麻烦,我们可不可以用一个方法来代替呢???
我们很多初学者都接触过这两个泛型,
List<int> list = new List<int>();
Dictionary<int, string> dic = new Dictionary<int, string>();
第一个是列表,第二个我们称为字典,那么我们可不可以自己定义一个泛型方法呢????
泛型的语法结构及定义
泛型语法: 泛型类或方法的后面 “<T>” T 是占位符,可以是任意字母,主要代表一个类型,如果是方法,参数就应当为占位符参数。 如 Show<T>(T t);方法,User<T> 类 。
泛型的特点:1.延迟声明,2使用的时候在声明
1.泛型方法的定义和使用
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { Show<int>(1); Show<double>(1.904); Show<DateTime>(DateTime.Now); Show<string>("wbc"); Console.Read(); } public static void Show<T>(T t) { Console.WriteLine($"typeName:{t.GetType().Name},Value:{t}"); } } }
我们会发现,完全可以运行,一个方法就搞定.那么我们可不可以省略 <int> ,答案是肯定的,当没有涉及到装箱拆箱操作的时候,我们完全可以省略,如下
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { Show(1); Show (1.904); Show(DateTime.Now); Show("wbc"); User u = new User(); Show(u); Console.Read(); } public class User { } public static void Show<T>(T t) { Console.WriteLine($"typeName:{t.GetType().Name},Value:{t}"); } } }
有人会说了,我使用Object 作为参数,一样可以实现,为什么不使用object呢,一切类型的父类是object ,应当可以的啊??,答案是肯定的,也是可以的,知所以不使用,是因为使用object类型的时候,会产生装箱拆箱操作,这个操作会损失精度。
2泛型类的定义和使用
泛型类的语法和泛型方法的语法很像,只是关键字是class ,如 Public Class MyGer<T>{
}
那么我们来看下泛型类的使用,看如下案列:
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { Person p = new Person() { Name="zhangsan",Age=28,Id=1,Id_No="110000198212193145",Address="beijiang",Sex="女" }; MyGrenric<Person> gren = new MyGrenric<Person>(); gren.Show(p); Console.Read(); } } /// <summary> /// 人的父类 /// </summary> public class BasePopleModel { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public string Id_No { get; set; } public string Sex { get; set; } } public class Pople { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public string Id_No { get; set; } public string Sex { get; set; } public override string ToString() { string result = "{"+$"name:{this.Name},age : {this.Age},sex : {this.Sex},no : {this.Id_No} "+"}"; return base.ToString(); } } //人的信息 public class Person : BasePopleModel { public int Id { get; set; } public override string ToString() { string result = "{ "+ $"name : {base.Name},age : {base.Age},sex : {base.Sex},no : {base.Id_No} , id : {this.Id}"+" }"; return result; } } public class MyGrenric<T> { public void Show(T t) { Console.WriteLine(t); } } }
看了上面的代码,我们可以看出,泛型类里面的方法,可以是普通方法和泛型方法,当普通方法使用泛型的时候,我们可以省略泛型,上诉代码替换为:
public void Show<T>(T t) {
Console.WriteLine(t);
}
是一样的结果。但是泛型类也有一个弊端,那就是失去了继承类的继承性质,如
BasePopleModel p = new Person()
{
Name="zhangsan",Age=28,Id=1,Id_No="110000198212193145",Address="beijiang",Sex="女"
};
MyGrenric<Person> gren = new MyGrenric<Person>();
gren.Show(p);
这个时候,我们的代码就会报错。那如何在泛型中保留类的基础性,使用如下语法就能做到:
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { BasePopleModel p = new Person() { Name="zhangsan",Age=28,Id=1,Id_No="110000198212193145",Address="beijiang",Sex="女" }; MyGrenric<BasePopleModel> gren = new MyGrenric<BasePopleModel>(); gren.Show(p); Console.Read(); } } /// <summary> /// 人的父类 /// </summary> public class BasePopleModel { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public string Id_No { get; set; } public string Sex { get; set; } } public class Pople { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public string Id_No { get; set; } public string Sex { get; set; } public override string ToString() { string result = "{"+$"name:{this.Name},age : {this.Age},sex : {this.Sex},no : {this.Id_No} "+"}"; return base.ToString(); } } //人的信息 public class Person : BasePopleModel { public int Id { get; set; } public override string ToString() { string result = "{ "+ $"name : {base.Name},age : {base.Age},sex : {base.Sex},no : {base.Id_No} , id : {this.Id}"+" }"; return result; } } public class MyGrenric<T> { public void Show(T t) { Console.WriteLine(t); } } public interface Iccc<S> { } }
当我们所有继承了BasePopleModel的子类,都可以使用这个泛型类。
3泛型接口的定义和使用
我们来看下代码,定义一个泛型接口,和定义泛型类的语法结构是一样的,他本身也具有接口的特性。代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp3 { public interface IBaseImpl<T> { int State { get; set; } void Show(T t); void Search<S>(S s,T t); } }
当我们普通类继承这个泛型接口的时候,我们会发现,继承不了,生成编译项目的时候,会提示出错误信息:如下图
那我们来试试,泛型类来继承泛型接口,看好使不好使,把我们上面创建的泛型类继承我们的接口(MyGrenric<T>:IBaseImpl<T>)如下:
public class MyGrenric<T>:IBaseImpl<T> { public int State { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public void Search<S>(S s, T t) { throw new NotImplementedException(); } public void Show(T t) { Console.WriteLine(t); } }
我们发现是能运行的.
现在问题来了,刚才我们的实例类继承泛型接口继承不了,我们的实例类继承泛型类能继承吗??,有没有什么办法,让我们的实例类继承我们的泛型接口和泛型类呢?我们先来看看,能不能实例类继承泛型类
我们会发现还是继承不了:既然前边说了,T只是一个占位符,我们可不可以显示的写出一个类呢??
我们发现,这样是可以的,没有编译错误,那么我泛型类和泛型接口的占位符都是T,那么我们使用不同 的类可以吗??,答案是否定的,绝对不可以,一个占位符只能代表一个类型,所以我们要使用不同的类型,就需要使用不同的占位符,如:
泛型约束
前边学习了这么多自定义泛型的知识,我们基本就把整个泛型学习完了,我们之前一直都是说T,S,是泛型的一个占位符,可以是任意类型,那我们可以限定这个类型吗?答案是肯定的,继续看图片
通过上诉,我们能看出,我们限定了类型,只能是Pople 类类型。限定语法,“只有在泛型类和接口之后跟WHERE 泛型占位符 :类型”。
where T: 类型值 | 说明 | 限定规范 |
class | 限定泛型只能是class 类型 | 可以有参数构造函数或无参数构造函数,不能和其他关键字一起使用 |
struct | 限定泛型只能是struct类型 | 不可以和其他类型一起使用 |
new() | 限定只能是类类型,切有无参数构造函数 | 必须有参数构造函数,不能和其他关键字一起使用 |
类类型 | 如果传入的是父类,则保留继承性质 | 无 |
值类型 | 无 | 无 |
这里就不过多演示上述内容,我们在这里只演示class ,代码如下
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { BasePopleModel p = new Person() { Name="zhangsan",Age=28,Id=1,Id_No="110000198212193145",Address="beijiang",Sex="女" }; MyGrenric<BasePopleModel> gren = new MyGrenric<BasePopleModel>(); gren.Show(p); Console.Read(); } } /// <summary> /// 人的父类 /// </summary> public class BasePopleModel { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public string Id_No { get; set; } public string Sex { get; set; } } public class Pople { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public string Id_No { get; set; } public string Sex { get; set; } public override string ToString() { string result = "{"+$"name:{this.Name},age : {this.Age},sex : {this.Sex},no : {this.Id_No} "+"}"; return base.ToString(); } } //人的信息 public class Person: BasePopleModel { public Person() { } public int Id { get; set; } public void Show(BasePopleModel s) { throw new NotImplementedException(); } public override string ToString() { string result = "{ "+ $"name : {base.Name},age : {base.Age},sex : {base.Sex},no : {base.Id_No} , id : {this.Id}"+" }"; return result; } } public class MyGrenric<T> where T : class ,new() { public void Show(T t) { Console.WriteLine(t); } } //public interface IBaseImpl<S> where S : class //{ // int State { get; set; } // void Show(S s); //} }
总结:泛型类必须继承在泛型类上,如果作为普通类的父类,必须显示指定其类型,在约束的时候,泛型类不能指定值类型的约束。
泛型接口必须被泛型类和泛型接口继承,如果被普通接口和普通类继承,必须显示的指定类型,必须放在多个继承文件的最后。在约束的时候,不能使用new()。
本文只是介绍常用的泛型使用方向,很多其他方向没有详细介绍,常用就是泛型类的使用,并不怎么涉及到泛型的继承,如果一个项目涉及到泛型继承,证明这个项目也是快重构了。不过在开发过程中,泛型的约束是经常使用的。仅供参考
另付2.0以后提供的常用泛型如下:
2.0版的.NET框架类库提供了一个新的命名空间,System.Collections.Generic,其中包含了一些已经可以使用的泛型容器类和相关的接口。和早期版本的.NET框架提供的非泛型容器类相比,这些类和接口更高效且是类型安全的。在设计、实现自定义的容器类之前,请你考虑是否使用或继承所列出类中的一个。
下面的表格列出了新的泛型类和接口,旁边是对应的非泛型类和接口。在一些地方要特别注意,如List<T>和Dictionary<T>,新泛型类的行为(behavior)与它们所替换的非泛型类有些不同,也不完全兼容。更详细的内容,请参考System.Collections.Generic的文档
泛型类或接口 |
描述 |
对应的非泛型类型 |
Collection<T> ICollection<T> |
为泛型容器提供基类 |
CollectionBase ICollection |
Comparer<T> IComparer<T> IComparable<T> |
比较两个相同泛型类型的对象是否相等、可排序。 |
Comparer IComparer IComparable |
Dictionary<K, V> IDictionary<K,V> |
表示用键组织的键/值对集合。 |
Hashtable IDictionary |
Dictionary<K, V>.KeyCollection |
表示Dictionary<K, V>中键的集合。 |
None. |
Dictionary<K, V>.ValueCollection |
表示Dictionary<K, V>中值的集合。 |
None. |
IEnumerable<T> IEnumerator<T> |
表示可以使用foreach 迭代的集合。 |
IEnumerable IEnumerator |
KeyedCollection<T, U> |
表示有键值的集合。 |
KeyedCollection |
LinkedList<T> |
表示双向链表。 |
None. |
LinkedListNode<T> |
表示LinkedList<T>中的节点。 |
None. |
List<T> IList<T> |
使用大小可按需动态增加的数组实现 IList 接口 |
ArrayList IList |
Queue<T> |
表示对象的先进先出集合。 |
Queue |
ReadOnlyCollection<T> |
为泛型只读容器提供基类。 |
ReadOnlyCollectionBase |
SortedDictionary<K, V> |
表示键/值对的集合,这些键和值按键排序并可按照键访问,实现IComparer<T>接口。 |
SortedList |
Stack<T> |
表示对象的简单的后进先出集合。 |
Stack |