一、泛型的理解。
- 我们的编码工作通常是这样的:1、定义一个类; 2、封装类的行为;3、创建类的实例。
但是我们定义的很多类具有相同的行为,我们自然就想到最好编好的行为代码能够适应不同的数据类型就好了。
所以泛型的意义就是将类的行为提取处理或重构,让代码能够处理不同数据结构。
- 就像类类型不是对象而是对象的模版,泛型也不是类型而是类型的模版。
- C#提供了5种泛型:类、结构、接口、委托和方法;前4个是类型,方法是成员。
二、泛型的声明和创建
泛型的使用:用“类型占位符”即类型参数书写代码,在创建类的实例时提供真实的类型。
创建和使用常规的、非泛型类的过程有两步:声明类和创建类的实例;但泛型类不是实际的类,而是类的模板,所以必须先构建实际类的类类型(类型实参),再创建这个构建后的类类型的实例。
泛型类声明时使用类型参数,创建时使用类型实参。
三、类型参数的约束
为了明确泛型能够处理的数据类型,我们需要提供额外的信息让编译器知道类型参数可以接受哪些类型,这些额外的信息叫做约束,只有符合约束的类型实参才能应用于类型参数。
约束使用where 关键字定义
- 在类型参数列表的“>”后面定义
- 同一个where子句有多个约束用逗号分隔,可以以任何次序列出。
- 多个where子句之间没有任何分隔符号
public struct MoneyType<T>
{
private T data;
public T Data
{
get{ return data;}
set { data = value; }
}
public MoneyType(T _value)
{
data = _value;
}
}
MoneyType<string> moneyType = new MoneyType<string>("五元钱");
var moneyNum = new MoneyType<int>(5);
listBox1.Items.Clear();
listBox1.Items.Add(moneyType.Data);
listBox1.Items.Add(moneyNum.Data.ToString());
五、泛型接口
与其他泛型相似,实现不同类型参数的泛型接口是不同的接口。
可以在非泛型类型中实现泛型接口
interface IMyfc<T>
{
T ReturnIt(T x);
}
public class Simple: IMyfc<int>, IMyfc<string>
{
#region IMyfc<int> 成员
public int ReturnIt(int x)
{
return x;
}
#endregion
#region IMyfc<string> 成员
public string ReturnIt(string x)
{
return x;
}
#endregion
}
Simple sim = new Simple();
listBox1.Items.Clear();
listBox1.Items.Add(sim.ReturnIt("五元钱"));
listBox1.Items.Add(sim.ReturnIt(5).ToString());
泛型接口的实现必须唯一。
public class Simple<M>: IMyfc<int>, IMyfc<string> 如果将<string>改成<M> 就会报错,因为:
“WindowsFormsApplication1.Simple<M>”不能同时实现“WindowsFormsApplication1.IMyfc<int>”和“WindowsFormsApplication1.IMyfc<M>”,原因是它们对于某些类型形参替换可以进行统一
六、泛型委托
要声明泛型委托,必须在委托名称之后、委托形参列表之前的尖括号前定义类型参数。
//返回类型 类型参数 委托形参
delegate R MyDelgate <T, R>(T value);
类型参数的范围包括:返回值、委托形参和约束子句。
注:类型参数最后一个才是对应返回类型的。
public delegate R Mydelegate<T, R>(T value);
public class testDel
{
public static string PrintString(string value)
{
return value;
}
public static string PrintName(string value)
{
return "Hello" + value;
}
}
var del = new Mydelegate<string, string>(testDel.PrintString);
listBox1.Items.Add(del("yuhaili"));
del += testDel.PrintName;
listBox1.Items.Add(del("caoag"));
七、泛型方法
我试着把
public static string PrintString(string value)
改成
public static R PrintString<T, R>(T value)
结果如何呢,编译器无法通过,因为它不知道R是什么,无法写return 语句。如果写成return R; 结果编译器说R是类型参数,不能当作变量使用。
说明有泛型方法,没有泛型函数
声明泛型方法: 类型参数 方法参数
类似于委托 public void myProc<T, S>(T x, S y){}
调用泛型方法:调用时提供类型实参。
myProc<int, string>(18, "name");
推断类型
如果我们为方法传人参数,编译器有时可以从方法参数的类型推断出泛型方法的类型参数的类型,这样就使得方法调用更简单,可读性更强
可以这样使用public class SimpleArray { public string myProc<T>(T[] arr) { Array.Reverse(arr); string sResult=""; foreach (T t in arr) { sResult += t.ToString(); } return sResult; } }
private void button7_Click(object sender, EventArgs e) { SimpleArray sim = new SimpleArray(); int[] intArray = new int[] {3,4,5,6,7 }; string[] sArray = new string[] { "One", "two", "three"}; listBox1.Items.Clear(); listBox1.Items.Add(sim.myProc<int>(intArray)); //调用泛型方法 listBox1.Items.Add(sim.myProc(intArray)); //引用类型并调用 listBox1.Items.Add(sim.myProc<string>(sArray)); //调用泛型方法 listBox1.Items.Add(sim.myProc(sArray)); //引用类型并调用 }
八、泛型方法和扩展方法
通常每个方法都和声明它的类关联,但扩展方法扩展了这个边界, 允许编写 和声明它的类之外的类 关联的方法。
简单讲,扩展方法就是在不改变原有类的声明的情况下,扩展类的行为。
扩展方法的三个要求
1、扩展方法必须被声明为static;
2、扩展方法声明所在的类也必须被声明为static;
3、扩展方法必须包含关键字this作为它的第一个参数类型,并在后面跟着它所扩展的类的名称。
class Holder<T> { T[] Vals = new T[3]; //泛型类的构造函数和非泛型类的构造函数声明是一样一样的。 public Holder(T vo, T v1, T v2) { Vals[0] = vo; Vals[1] = v1; Vals[2] = v2; } public T[] GetValues() { return Vals; } } static class ExtendHolder { public static void Print<T>(this Holder<T> h, ListBox lstBox) { foreach (T m in h.GetValues()) { lstBox.Items.Add(m.ToString()); } } }
private void button8_Click(object sender, EventArgs e) { Holder<int> holder = new Holder<int>(1,3,4); ExtendHolder.Print<int>(holder, listBox1); Holder<string> holders = new Holder<string>("ddd", "mmmm", "nnnn"); holders.Print<string>(listBox1); //注:两种调用方式: 1是静态类.方法名称(参数列表); 2是在对象自身上调用实例方法,后一种可读性更好。 //Print后面<int>和<string>可以不写,因为在类型参数和方法参数的数据类型相同的情况下,泛型方法可以根据方法参数的数据类型推断出类型参数的类型实参类型。 }