C#基础知识之泛型
分类:
IT文章
•
2022-05-21 10:06:56
泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用。
其实官方文档说明的很详细,我这边算是做个记录吧
一、什么是泛型?
泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个非常重要的新功能。
泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。您可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。
简单的说,我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。
二、为什么使用泛型?
我们先来看一下下面的代码
static void Main(string[] args)
{
#region 方式一 普通方法
ShowInt(1);
ShowStr("string1");
#endregion
Console.ReadKey();
}
public static void ShowInt(int num)
{
Console.WriteLine("输出INT型数据:{0}", num);
}
public static void ShowStr(string str)
{
Console.WriteLine("输出String型数据:{0}",str);
}
View Code
这两个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.1版的时候,还没有泛型这个概念,那么怎么办呢。就有人想到了OOP三大特性之一的继承,C#语言中,所有类型都源自同一个类型,那就是object。其实就是一个装箱拆箱的过程,会损耗一些性能。
static void Main(string[] args)
{
#region 方式二 继承,C#语言中,所有类型都继承自object。
ShowObj(2);
ShowObj("string2");
#endregion
Console.ReadKey();
}
public static void ShowObj(object obj)
{
Console.WriteLine("输出的类型{0},值{1}",obj.GetType(),obj);
}
View Code
微软在2.0的时候发布了泛型。接下来我们用泛型方法来实现该功能。下面是一个简单的泛型示例:
static void Main(string[] args)
{
#region 方式三 泛型
ShowInfo(3);
ShowInfo("STRING3");
ShowInfo<int>(4);
ShowInfo<string>("STRING4");
#endregion
Console.ReadKey();
}
public static void ShowInfo<T>(T para)
{
Console.WriteLine("输出的类型{0},值{1}",para.GetType(),para);
}
View Code
三、泛型的特性
使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:
- 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
- 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
- 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 您可以对泛型类进行约束以访问特定数据类型的方法。
- 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
四、泛型类型参数
在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定特定类型的占位符。泛型类无法按原样使用,因为他不是真正的类型,他更像是类型的蓝图,若要使用GenericList<T>,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。此特定类的类型参数可以是编译器可识别的任何类型,可创建任意数量的构造类型实例,其中每个使用不同的类型参数。在 GenericList<T> 的每个实例中,类中出现的每个 T 在运行时均会被替换为类型参数。 通过这种替换,我们已通过使用单个类定义创建了三个单独的类型安全的有效对象。
GenericList<float> list1 = new GenericList<float>(); GenericList<ExampleClass> list2 = new GenericList<ExampleClass>(); GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
五、泛型类型参数约束
1、什么是泛型类型参数约束
约束告知编译器类型参数必须具备的功能。在没有约束的情况下,类型参数可以是任何类型,编译器只能假定Object的成员,object是任何.NET类型的最终基类。如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。通过使用where上下文关键字指定约束。下表列出了其中类型的约束:

某些约束是互斥的。所有值类型必须具有可访问的无参数构造函数。struct约束包含new()约束,且new()约束不能与struct约束结合使用。unmanaged约束包含struct约束。unmanaged约束不能与struct或new()约束结合使用。
2、使用泛型参数约束的原因
通过约束类型参数,可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或调用System.Object不支持的任何方法,则必须对该类型参数应用约束。例如,基类约束告诉编译器,仅此类型的对象或派生自此类型的对象可用作类型参数。编译器有了此保证后,就能够允许在泛型类中调用该类型的方法。 基类约束的例子如下:
3、各个泛型参数约束简介
(1)where T:类(类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。)
class Program
{
static void Main(string[] args)
{
#region 基类约束实例
int stuNum = 10;
int teacherNum = 12;
SchoolGeneric<BeijingSchool> beijingSchool = new SchoolGeneric<BeijingSchool>();
beijingSchool.Add(new BeijingSchool(stuNum,teacherNum, "beijing",true));
beijingSchool.FindSchoolByNum(stuNum);
#endregion
Console.ReadKey();
}
}
class School
{
private int _studentNum;
private int _teacherNum;
private string _schoolName;
public int StudentNum
{
get { return this._studentNum; }
set { this._studentNum = value; }
}
public int TeacherNum
{
get { return this._teacherNum; }
set { this._teacherNum = value; }
}
public string SchoolName
{
get{ return this._schoolName; }
set { this._schoolName = value; }
}
public School(int studentNum,int teacherNum,string schoolName)
{
this.StudentNum = studentNum;
this.TeacherNum = teacherNum;
this.SchoolName = schoolName;
}
}
class BeijingSchool:School
{
private bool isPrivateSchool;
public bool IsPrivateSchool
{
get { return isPrivateSchool; }
set
{
isPrivateSchool = value;
}
}
public BeijingSchool(int stuNum, int teacherNum,string schoolName,bool isPrivateSchool) : base(stuNum, teacherNum, schoolName)
{
this.IsPrivateSchool = isPrivateSchool;
}
}
class SchoolGeneric<T> where T : School
{
T[] TList;
int end;
public SchoolGeneric()
{
this.TList = new T[1];
end = 0;
}
public void Add(T school)
{
TList[end] = school;
end++;
}
public void FindSchoolByNum(int StuNum)
{
Console.WriteLine("学生人数满足:{0} 的学校有:", StuNum);
for (int i = 0; i < end; i++)
{
if (TList[i].StudentNum == StuNum)
{
Console.WriteLine(TList[i].SchoolName);
}
}
}
}
View Code
(2)where T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。)
class PersonGeneric<T> where T:struct
{
public void Print(T person)
{
Console.WriteLine("{0}",person);
}
}
class Program
{
static void Main(string[] args)
{
#region where T : struct 类型参数必须是值类型,可以指定除 Nullable<T> 以外的任何值类型
PersonGeneric<int> personAge = new PersonGeneric<int>();
personAge.Print(23);
PersonGeneric<double> personName = new PersonGeneric<double>();
personName.Print(12.21);
#endregion
Console.ReadKey();
}
}
View Code
(3)where T:new()(类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。)
class People<T> where T : Person, System.IComparable<T>, new()
{
// ...
}
View Code
(4)where T:<接口名称>(类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。)
interface IMyInterface
{
}
class Dictionary<TKey, TVal> where TKey : IComparable,IEnumerable where TVal : IMyInterface
{
public void Add(TKey key,TVal val)
{
Console.WriteLine("key:{0},val:{1}",key,val);
}
}
class Program
{
static void Main(string[] args)
{
#region where T:new()(类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。)
SplitLine("where T:new()");
#endregion
Console.ReadKey();
}
}
View Code
(5)where T:U(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。也就是说T和U的参数必须一样)
class List<T>
{
void Add<U>(List<U> items) where U : T {/*...*/}
}
View Code
六、泛型类
通常,创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。创建自己的泛型类时,需要考虑以下重要注意事项:
通常,可参数化的类型越多,代码就越灵活、其可重用性就越高。但过度泛化会造成其他开发人员难以阅读或理解代码。
其中一个有用的规则是,应用最大程度的约束,同时仍可处理必须处理的类型。例如,如果知道泛型类仅用于引用类型,则请应用类约束。这可防止将类意外用于值类型,并使你可在 T 上使用 as 运算符和检查 null 值。
因为泛型类可用作基类,所以非泛型类的相同设计注意事项在此也适用。请参阅本主题后文有关从泛型基类继承的规则。
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }
//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }
View Code
七、泛型方法
泛型方法是通过类型参数声明的方法
static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
public static void TestSwap()
{
int a = 1;
int b = 2;
Swap<int>(ref a, ref b);
System.Console.WriteLine(a + " " + b);
}
View Code
八、泛型接口
为泛型集合类或表示集合中的项的泛型类定义接口通常很有用处。 为避免对值类型的装箱和取消装箱操作,泛型类的首选项使用泛型接口,例如 IComparable<T>而不是 IComparable。 .NET Framework 类库定义多个泛型接口,以将其用于 System.Collections.Generic 命名空间中的集合类。接口被指定为类型参数上的约束时,仅可使用实现接口的类型。 如下代码示例演示一个派生自 GenericList<T> 类的 SortedList<T> 类。 SortedList<T> 添加约束 where T : IComparable<T>。这可使 SortedList<T> 中的 BubbleSort 方法在列表元素上使用泛型 CompareTo 方法。 在此示例中,列表元素是一个实现 IComparable<Person> 的简单类 Person。
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
protected Node head;
protected Node current = null;
protected class Node
{
public Node next;
private T data;
public Node(T t)
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
public T Data //T as return type of property
{
get { return data; }
set { data = value; }
}
}
public GenericList()
{
head = null;
}
public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
public void BubbleSort()
{
if (null == head || null == head.Next)
{
return;
}
bool swapped;
do
{
Node previous = null;
Node current = head;
swapped = false;
while (current.next != null)
{
if (current.Data.CompareTo(current.next.Data) > 0)
{
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}
public class Person : System.IComparable<Person>
{
string name;
int age;
public Person(string s, int i)
{
name = s;
age = i;
}
// This will cause list elements to be sorted on age values.
public int CompareTo(Person p)
{
return age - p.age;
}
public override string ToString()
{
return name + ":" + age;
}
// Must implement Equals.
public bool Equals(Person p)
{
return (this.age == p.age);
}
}
class Program
{
static void Main()
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();
//Create name and age values to initialize Person objects.
string[] names = new string[]
{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
//Populate the list.
for (int x = 0; x < 10; x++)
{
list.AddHead(new Person(names[x], ages[x]));
}
//Print out unsorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with unsorted list");
//Sort the list.
list.BubbleSort();
//Print out sorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with sorted list");
}
}
View Code
九、泛型和数组
在 C# 2.0 和更高版本中,下限为零的单维数组自动实现 IList<T>。 这可使你创建可使用相同代码循环访问数组和其他集合类型的泛型方法。 此技术的主要用处在于读取集合中的数据。 IList<T> 接口无法用于添加元素或从数组删除元素。 如果在此上下文中尝试对数组调用 IList<T> 方法(例如 RemoveAt),则会引发异常。如下代码示例演示具有 IList<T> 输入参数的单个泛型方法如何可循环访问列表和数组(此例中为整数数组)。
class Program
{
static void Main()
{
int[] arr = { 0, 1, 2, 3, 4 };
List<int> list = new List<int>();
for (int x = 5; x < 10; x++)
{
list.Add(x);
}
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
static void ProcessItems<T>(IList<T> coll)
{
System.Console.WriteLine
("IsReadOnly returns {0} for this collection.",
coll.IsReadOnly);
foreach (T item in coll)
{
System.Console.Write(item.ToString() + " ");
}
System.Console.WriteLine();
}
}
View Code
十、泛型委托
委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样,如以下示例中所示:
public delegate void Del<T>(T item);
public static void Notify(int i) { }
Del<int> m1 = new Del<int>(Notify);
View Code