泛型
泛型是什么?
即通过参数化类型来实现在同一份代码上操作多种数据类型。
泛型可以定义类型安全类,而不会损害类型安全、性能或工作效率。
泛型怎么写?
泛型类
class MyClass
{
//.............
}
泛型方法
public void MyMethod()
{
//.............
}
泛型接口
interface MyInterface
{
//.............
}
泛型结构
struct MyStruct
{
//.............
}
相对于C#中的类的声明等语法,其实泛型只是多加了一个类型参数用来表示不同类型而已。添加的方式也就是在类、方法等名称后面加
泛型的使用
对于泛型的使用,总结成一句话就是,跟普通类的使用是几乎一样的,只是在使用过的时候需要将类型参数确定为明确的类型。
泛型就是一个
泛型的优点
性能
泛型 的一个优点是性能。
对值类型使用 非泛型集合类 在把值类型转换为引用类型,和把引用类型转换为值类型时需要进行装箱和拆箱操作。
装箱拆箱链接。
值存储在栈上,引用存储在堆上。
泛型在使用时定义具体的类型,避免装箱拆箱操作。
类型安全
泛型类型需要在使用时指定,只能使用泛型指定的类型,编译器会在编译前检测,提前发现错误。
二级制代码重用
泛型可以更好的重用二级制代码。泛型类可以定义一次,并且享有许多不同的类型实例化。
代码扩展
泛型类的定义会放在程序集中,所以使用特定类型实例化泛型类不会在IL代码中重复这些类。
编译器将泛型类编译为本地代码后会给每一个值创建一个新类,引用类型共享一个本地类的所有实现代码。
因为引用类型的泛型类实例化只需要4个字节内存地址,就可以引用一个引用类型。
而对于值类型的存在于泛型类实例化内存中,每个值类型对内存的要求不同,所以要为每一个类创建一个新类。
引用类型大小确定,有限,所以引用类型共享代码。值类型大小不固定,每个值类型的实例化都会创建新类。
命名约定
- 泛型类型使用字母T作为前缀。
- 如果没有特殊要求,泛型类型允许使用任意类型代替,且只使用了一个泛型类型,就可以用字符T作为泛型类型的名称。
- 如果泛型类型有特定的要求,或者使用了两个或多个泛型类型,就应该给泛型类型使用描述性名称。
using System;
using static System.Console;
namespace ConsoleApp22
{
class Program
{
public class MyClass
{
public T1 X1;
public T2 X2;
}
static void Main(string[] args)
{
MyClass s = new MyClass();
s.X1 = 100;
s.X2 = "ASD";
WriteLine(s.X1);
WriteLine(s.X2);
ReadKey();
}
}
}
描述性名称并没有具体含义,但是要尽量表明其期望的含义,正常应该写为Tint,Tstring
泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许编写一个可以与任何数据类型一起工作的类或方法。
可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。
泛型 可以创建独立于被包含类型的类和方法,我们不必给不同的类型编写许多相同的方法或类,只创建一个方法或类即可。
另一个减少代码的选项是使用object类,但使用派生自object类的类型进行传递不是类型安全的。
泛型类 使用 泛型类型 ,并且可以根据特定的类型替换泛型类型,保证了安全性。
如果这个 泛型 不支持 泛型类 ,编译器就会出现错误。
泛型 不局限于类,还有用于接口和方法的 泛型。以及用于委托的泛型
委托的泛型链接
泛型类
创建泛型类
首先介绍一个一般的,非泛型的简化链表类,它可以包含任意多的对象,以后再把这个类转化为泛型类。
在链表中,一个元素引用下一个元素。
先创建一个类,这部分可以直接看C#实现链表
类中包含链表需要的元素。
public class LinkedListNode
{
public LinkedListNode(object value)
{
Value = value;
}
public object Value { get; set; }//值
public LinkedListNode Next { get; internal set; }//指向下一个
public LinkedListNode Prev { get; internal set; }//指向上一个
}
然后实现具体的链表。
public class LinkedList : IEnumerable//IEnumerable 是 .NET 的接口,用于实现迭代
{
public LinkedListNode First { get; set; }//链表头
public LinkedListNode Last { get; set; }//链表尾
public LinkedListNode AddLast(object node)//创建链表
{
var newNode = new LinkedListNode(node);//对链表数量化
if (First == null)//如果头为空
{
First = newNode;//让链表头为新链表
Last = First;//链表头和链表尾相等
}
else//如果链表头部位空
{
Last.Next = newNode;//尾链表的下一个元素为新链表
Last = newNode;//尾链表尾新链表
}
return newNode;//返回链表
}
public IEnumerator GetEnumerator()//迭代方法获取链表
{
LinkedListNode current = First;//获取链表头
while (current != null)//当前链表不为空
{
yield return current.Value;//迭代返回链表的下一个元素
current = current.Next;//当前元素为下一个元素
}
}
实现链表头尾,扩展链表,链表遍历通过 枚举器 详情见 c#数组和元组
然后就可以对链表进行操作,
var list1 = new LinkedList();
list1.AddLast(2);
list1.AddLast(4.78);
list1.AddLast("6");
foreach (var i in list1)
{
WriteLine(i);
}
接下来我们创建泛型类版本的链表。
using System;
using System.Collections;
using System.Collections.Generic;
using static System.Console;
namespace ConsoleApp22
{
class Program
{
public class LinkedListNode//定义一个链表泛型类
{
public LinkedListNode(T value)//泛型类的构造函数
{
Value = value;
}
public T Value { get; }//只读字段链表值
public LinkedListNode Next { get; internal set; }//指向下一个元素指针
public LinkedListNode Prev { get; internal set; }//指向上一个元素指针
}
public class LinkedList : IEnumerable//链表具体实现
{
public LinkedListNode First { get; private set; }//链表类型的链表头
public LinkedListNode Last { get; private set; }//链表类型的链表尾
public LinkedListNode AddLast(T node)//链表方法,返回一个链表
{
var newNode = new LinkedListNode(node);//对链表进行实例化
if (First == null)//通过头结点为空
{
First = newNode;//头结点为链表
Last = First;//尾结点就是头结点
}
else//如果头结点不为空
{
Last.Next = newNode;//尾结点的下一个结点为新传入的结点
Last = newNode;//尾结点为这个新链表
}
return newNode;//返回新列表
}
public IEnumerator GetEnumerator()//对链表遍历方法,返回一个可迭代泛型集合
{
LinkedListNode current = First;//当前指针获取头结点
while (current != null)//如果结点存在
{
yield return current.Value;//迭代返回当前结点的值
current = current.Next;//当前结点转向下一个结点
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
static void Main()
{
var list2 = new LinkedList();//对泛型实例化,规定链表只能传入int
list2.AddLast(1);//撰写链表
list2.AddLast(3);
list2.AddLast(5);
foreach (int i in list2)//遍历链表的值
{
WriteLine(i);
}
var list3 = new LinkedList();//只能传入字符串
list3.AddLast("2");
list3.AddLast("four");
list3.AddLast("foo");
foreach (string s in list3)
{
WriteLine(s);
}
ReadKey();
}
}
}
泛型类的定义与一般类类似,只是要使用泛型声明。
之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型,
public class LinkedListNode//定义一个链表泛型类
{
public T Value;
}
构造函数也可以变为接受T类型的对象,也可以返回和设置泛型类型。
public LinkedListNode(T value)//泛型类的构造函数
{
Value = value;
}
泛型类的功能
默认值
不能将null赋予泛型类型,因为泛型可以实例化为值类型,而null只能用于引用类型。
可以使用 default 关键字将 null在示例化为值类型时变成0.
class Test
{
public T F()
{
T t = null; //有问题
return t;
}
}
t在这里无法编译,无法将null转换为参数类型T,因为它可能是不可以为null的值类型。
所以需要使用 default
class Test
{
public T F()
{
return default(T);
}
}
default对应各种类型生成默认值列表如下
类型 | 默认值 |
---|---|
任何引用类型 | null |
数值类型 | 0 |
bool | false |
enum | 表达式(E)0生成的值,其中E是枚举标识符 |
struct | 将所有值类型设为其默认值,将所有引用类型设为null |
可以为null的类型 | HasValue属性为false,且Value属性未定义的实例 |
约束
如果泛型类需要调用泛型类型的方法,就必须添加约束
泛型接受的约束类型
约束 | 说明 |
---|---|
where T : sturct | 对于结构约束,泛型必须是值类型 |
where T : class | 类约束指定类型T必须是引用类型 |
where T : IFoo | 指定类型T必须实现接口IFoo |
where T : Foo | 指定类型T必须派生自基类Foo |
where T : new() | 这个是一个构造函数约束,指定类型T必须有一个默认构造函数 |
where T1: T2 | 这个约束也可以指定,类型T1派生自泛型类型T2 |
**只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束。
使用泛型类型还可以合并多个约束。
class MyClassy
where T1: struct
where T2: class,new()
{
//....
}
指定T1必须是值类型,T2必须是引用类型且必须有一个默认构造函数。
**在C#中,where子句的一个重要限制是不能定义必须由泛型类型实现运算符,运算符不能在接口定义 。在where子句中,只能定义基类,接口和默认构造函数,
继承
泛型类可以实现泛型接口,也可以派生自一个类,泛型类可以派生自泛型基类。
public class MyClass1
{
//.......
}
public class MyClass2 : MyClass1
{
//.......
}
派生类不必是泛型类
public class MyClass1
{
//.......
}
public class MyClass2 : MyClass1
{
//.......
}
泛型接口同样可以被继承
interface IMethod
{
void f();
}
public class MyClass3 : IMethod
{
public void f()
{
WriteLine(777);
}
}
静态成员
泛型类的静态成员需要特别关注,泛型类型的静态成员只能在类中的一个实例中共享,
using System;
using static System.Console;
namespace ConsoleApp23
{
class Program
{
public class MyClass
{
public static T x;
}
static void Main(string[] args)
{
MyClass.x = 100;
MyClass.x = "asdw";
ReadKey();
}
}
}
string,int是两个不同的类型,这样就出现两个不同的静态字段x。
泛型接口
使用泛型可以定义接口,在接口中定义的方法可以带泛型参数,
泛型接口的协变和抗变
参数类型是协变的。
泛型参数定义的类型只能作为方法的返回类型,不能作为方法的参数类型,且该类型直接或者间接地继承自接口方法的返回值类型;可以使用out关键字声明协变参数
如果泛型类型使用 out 关键字做标注,泛型接口就是协变的,这也意味着返回类型只能是T。
如:string->object (子类到父类的转换)
返回的方法是抗变的。
泛型参数定义的类型只能作为方法参数的类型,不能作为返回值类型,且该类型是接口方法的参数类型的基类型;可以使用in关键字声明抗变参数。
如果泛型类型使用 in 关键字做标注,泛型接口就是抗变的,这也意味接口只能将类型T用作其方法的输入。
如:object->string (父类到子类的转换)
泛型结构
与类类似,结构也可以是泛型的,非常类似于泛型类,但是没有继承。
using System;
using static System.Console;
namespace ConsoleApp23
{
class Program
{
public struct MyStruct
{
public T A;
public T B;
}
static void Main(string[] args)
{
MyStruct s;
int a = s.A = 100;
int b = s.B = 777;
WriteLine(a);
WriteLine(b);
ReadKey();
}
}
}
泛型方法
在泛型方法中,泛型类型用方法声明来定义。
泛型方法可以在非泛型类中定义。
泛型方法示例
using System;
using static System.Console;
namespace ConsoleApp23
{
class Program
{
public class MyClass
{
public static void swap(ref T a,ref T b)
{
T temp;
temp = a;
a = b;
b = temp;
}
}
static void Main(string[] args)
{
int a = 4;
int b = 10;
WriteLine("{0} {1}",a,b);
MyClass.swap(ref a, ref b);
WriteLine("{0} {1}",a,b);
ReadKey();
}
}
}
这里为什么要用ref。ref关键字用于将方法内的变量改变后带出方法外。
带约束的泛型方法
泛型类可以用where子句限制,泛型方法也可以用where子句限制,
public static void swap(ref T a,ref T b) where T: struct
{
T temp;
temp = a;
a = b;
b = temp;
}
带委托的泛型方法
https://www.jianshu.com/p/5a12f3f74c62
泛型方法规范
泛型方法可以重载,为特定类型定义规范,这也适用于带泛型的方法。
public void F(T x)
{
WriteLine(x);
}
public void F(int x)
{
WriteLine(x);
}
public void F( T1 x1,T2 x2)
{
WriteLine("{0} {1}",x1,x2);
}
public void F(int x1,int x2)
{
WriteLine("{0} {1}", x1, x2);
}
上面完成F方法的四次重载。
在编译期间,编译器会使用最佳匹配。
泛型脑涂概览
脑图转载自 牧白 # C#-弄懂泛型和协变、逆变