概述:C#中泛型的使用。
一 什么是泛型
通过参数化类型来实现在同一份代码上操作多种数据类型。利用“参数化类型”将类型抽象化,从而实现灵活的复用。
二 泛型的特点
- NET 自从2.0版本就开始支持泛型。
- 泛型不仅是C#编程语言的一部分,而且是CLR定义的。即若泛型类在C#中定义,也可以在VB中用一个特定的类型实例化。
- 泛型不仅限于类,接口、方法、结构、委托都可以是泛型的。
三 使用泛型的好处
- 不必给不同类型编写功能相同的许多方法和类,只需创建一个方法或类即可。
- 类型安全(相比于Object类)
- 性能 避免不必要的装箱和拆箱
- 抽象之后,更容易修改代码。
四 泛型类的定义
1.泛型的命名约定
- 泛型类型的名称用 T 作为前缀
- 若只使用一个泛型类型且没有特殊要求,可用 T 代替泛型类型名称;若泛型类型有特殊要求或者使用了多个泛型类型,就应该是用描述性的名称
2.创建泛型类
public class Generic<T>
{
public List<T> List { get; set; }
public Generic(T data)
{
if (List == null)
{
List = new List<T>();
}
List.Add(data);
}
}
五 泛型类的功能
1.默认值
- 不能把 null 赋值给泛型类型(变量),因为泛型类型可以实例化为值类型,而
null
只能用于引用类型。 - 如果要给泛型(变量)赋值默认值,那么使用
default
关键字,那么值类型赋值为0
,引用类型赋值为null
。
public class Generic<T>
{
public List<T> List { get; set; }
public static T BaseGeneric = default(T);
public Generic(T data)
{
if (List == null)
{
List = new List<T>();
}
List.Add(data);
}
}
2.约束
- 约束泛型类型的类型,如 必须实现某个接口、必须是引用、必须是值类型、必须派生自某个基类、必须有一个默认的构造函数。
约束 | 说明 |
---|---|
where T : struct | 结构约束,类型T必须是值类型。 |
where T : class | 类约束,类型T必须是引用类型。 |
where T : IFoo | 类型T必须实现接口IFoo。 |
where T : Foo | 类型T必须派生自基类Foo。 |
where T : new() | 构造函数约束,类型T必须有一个默认构造函数。 |
where T1 : T2 | 裸型约束, 类型T1必须派生自泛型类型T2. |
注意:只能为默认构造函数定义构造函数约束,其他的构造函数不能对顶构造函数约束。
public class Generic<T, T2> where T : class, IFoo, IFoo, T2 where T2 : class, new()
{
//new()约束必须是最后一个约束
//class 和 struct约束必须在其他约束之前
// class 和 struct是不能同时存在的(理论)
public List<T> List { get; set; }
public static T BaseGeneric = default(T);
public Generic(T data)
{
if (List == null)
{
List = new List<T>();
}
List.Add(data);
}
}
public interface IFoo
{
}
public class Foo : IFoo
{
}
3.继承
泛型类型可以实现泛型接口,也可以派生自一个类。 要求必须重复接口的泛型类型,或者必须指定基类的类型。 所以派生出来的子类,可以是泛型的也可以不是泛型的。
public class Father<T> { }
//重复接口(基类)的泛型类型
public class SonOne<T> : Father<T> { }
//指定接口(基类)的泛型类型
public class SonTwo : Father<object> { }
4.静态成员
泛型的静态成员只能在类的一个实例中共享。
public class StaticDemo<T>
{
//泛型类的静态字段
public static int X;
}
class Program
{
static void Main(string[] args)
{
StaticDemo<string>.X = 1;
StaticDemo<int>.X = 2;
Console.WriteLine(StaticDemo<string>.X);//2
}
}
六 泛型类的使用(Demo)
class Program
{
static void Main(string[] args)
{
IList<int> list = new List<int>() { 9, 5, 4, 7, 1, 2, 5, 3, 2, 5, 8, 6, 5, 4, 7, 8 };
for (int i = 0; i < list.Count; i++)
{
Console.Write("{0} , ", list[i]);
}
Console.WriteLine();
BubbleSort<int>.Sort(list);
for (int i = 0; i < list.Count; i++)
{
Console.Write("{0} , ", list[i]);
}
Console.ReadKey();
}
}
public class BubbleSort<T> where T : IComparable<T>
{
public static void Sort(IList<T> list)
{
int count = list.Count;
for (int k = 0; k < count - 1; k++)
{
for (int i = 0; i < count - 1; i++)
{
if (list[i].CompareTo(list[i + 1]) > 0)
{
T temp = list[i];
list[i] = list[i + 1];
list[i + 1] = temp;
}
}
}
}
}
七 C#中的泛型
**1.泛型结构(Nullable)**
- 结构也可以是泛型的。
- 场景:数据库和XML文件中数字和编程语言中的数字有显著不同的特性,编程语言中的数字不能为null,但是数据库和XML中的数字都可以为null
Nullable<T>
定义了一个约束:其中的泛型类型T必须是一个结构。- 可空类型可以与算术运算符一起使用,如果其中一个是null,那么结果就是null。
- 可空类型转换为T时候,需要强制转换,或者使用合并运算符(??)
Nullalbe<T>
可是使用int?
bool?
datetime?
等代替
static void Main(string[] args)
{
Nullable<int> nullInt = null;
Nullable<long> nullLong = null;
int numberInt = nullInt ?? 0;
long numberLong = 0;
if (nullLong.HasValue)
{
numberLong = nullLong.Value;
}
Console.ReadKey();
}
八 泛型的协变和抗变
本节主要参看博文
1.泛型协变的特点
- 以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。
- 当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。
- 值类型不参与逆变与协变。
2.下面只是我自己的总结,
详情可以看引用的博文。
- 协变 T只能作为方法的返回类型 Foo<父类>=Foo<子类>子类>父类>
- 抗变 T只能作为方法的参数 Foo<子类>=Foo<父类>父类>子类>
- 协变和抗变的根本是利用子类的引用能安全第赋值给父类的引用。