泛型允许我们延迟编写类或方法中的参数类型,直到在程序中使用它的时候,模块内高内聚,模块间低耦合
示例:
var list= new ArrayList();
list.Add(44);//装箱
int il=(int)list[0];//拆箱
foreach(int i2 in list)
{
Console.writeLine(i2);// 拆箱
}
装箱和拆箱操作很容易使用,但性能损失比较大,遍历许多项时尤其如此。
System.Collections.Generic名称空间中的ListT>类不使用对象,而是在使用时定义类型。在下面的例子中ListT>类的泛型类型定义为int,所以int类型在JITJust-In-Time)编译器动态生成的类中使用,不再进行装箱和
拆箱操作:
var list = new List<int>();
list.Add(44);//不再装箱存在List
int i1 = list[0]: // 不再拆箱无需转换
foreach(int i2 in list)
{
Console.WriteLine(i2);
}
泛型的另一个特性是类型安全。与ArrayList类一样,如果使用对象,就可以在这个集合中添加任意类型。下面的例子在ArrayList类型的集合中添加一个整数、一个字符串和一个MyClass类型的对象:
var list=new ArrayList();
list.Add(44);
list.Add("mystring");
list.Add(new MyClass());
foreach(int i2 in list)
{
Console.WriteLine(i2); //运行时异常
}
在泛型类List中,泛型类型T定义了允许使用的类型。有了List的定义,就只能把整数类型添加到集合中。编译器不会编译这段代码,因为Add0方法的参数无效:
var list=new List<int>();
list.Add(44);
list.Add(”mystring”);//无法编译
list.Add(new MyClass());//无法编译
泛型允许更好地重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。不需要像C++模板那样访问源代码。例如,System.Collections.Generic名称空间中的List类用一个int、一个字符串和一个MyClass类型实例化:
var list=new List<int>();
list.Add(44);
var stringList=new List<string>();
stringList.Add(”mystring”);
var myClassList = new List<MyClass>();
myClassList.Add(new MyClass());
在用不同的特定类型实例化泛型时,会创建多少代码?因为泛型类的定义会放在程序集中,所以用特定类型实例化泛型类不会在Ⅱ代码中复制这些类。但是,在ⅡIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类。引用类型共享同一个本地类的所有相同的实现代码。这是因为引用类型在实例化的泛型类中只需要4个字节的内存地址(32位系统),就可以引用一个引用类型。
值类型包含在实例化的泛型类的内存中,同时因为每个值类型对内存的要求都不同,所以要为每个值类型实例化一个新类。
泛型类型的名称用字母T作为前缀,如果没有特殊的要求,泛型类型允许用任意类替代,且只使用了一个泛型类型,就可以用字符T作为泛型类型的名称。
public class List{}
public class LinkedList{}
如果泛型类型有特定的要求(例如,它必须实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称
public delegate void EventHandler<TEventArgs> (object sender , TEventArgs e);
public delegate Toutput Converter<TInput,Toutput>(TInput from);
public class SortedList<TKey,TValue>{ }
泛型类的定义与一般类类似,只是要使用泛型类型声明。之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型。LinkedListNode类用一个泛型类型T声明。属性Value的类型是T,而不是object。构造函数也变为可以接受T类型的对象。也可以返回和设置泛型类型,所以属性Next和Prev的类型是LinkedListNode。
public class LinkedListNode<T>
{
public LinkedListNode(T value) =>
Value = value;
public T Value { get; }
public LinkedListNode<T> Next { get; internal set; }
public LinkedListNode<T> Prev { get; internal set; }
}
泛型类型既可以实例化为引用类型也可以实例化为值类型。而null只能赋予引用类型,0只能赋予值类型
这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。
public T GetDocument()
{
T doc = default;
return doc;
}
注意:
default关键字根据上下文可以有多种含义。switch语句使用default定义默认情况。在泛型中,取决于泛型类型是引用类型还是值类型,泛型default将泛型类型初始化为null或0。
如果泛型类需要调用泛型类型中的方法,,就必须添加约束.
注意:
只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束
使用泛型类型还可以合并多个约束。whereT:IFoo,new() 约束和MyClass声明指定,类型T必须实现IFoo接口,且必须有一个默认构造函数。
public class MyClass<T> where T:IFoo,new()
{
}
泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类
public class Base<T>{}
public class Derived<T>:Base<T>{}
其要求是必须重复接口的泛型类型,或者必须指定基类的类型,如下例所示:
public class Base<T>{}
public class Derived<T>: Base<string>{}
于是,派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体
的类实现。这允许对特定类型执行特殊的操作:
public abstract class Calc<T>
{
public abstract T Add(T x,T y);
public abstract T Sub(T x,T y);
}
public class IntCalc: Calc<int>
{
public override int Add(int x,int y)=>x+y;
public override int Sub(int x,int y)=>x-y;
}
还可以创建一个部分的特殊操作,如从Query中派生StringQuery类,只定义一个泛型参数,如字符串
TResult。要实例化StringQuery,只需要提供TRequest的类型:
public class Query<TRequest,TResult>
{
}
public StringQuery<TRequest>:Query<TRequest , string>
{
}
泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。下面看一个例子,其中
StaticDemo类包含静态字段x:
public class StaticDemo<T>
{
public static int x;
}
由于同时对一个string类型和一个int类型使用了StaticDemo类,因此存在两组静态字段
StaticDemo<string>.x=4;
StaticDemo<int>.x=5;
Console.WriteLine(StaticDemo<string>.x); //4
使用泛型可以定义接口,在接口中定义的方法可以带泛型参数。在链表的示例中,实现了IEnumerable
接口,它定义了GetEnumerator(0方法,以返回IEnumerator。NET为不同的情况提供了许多泛型接口,例
如,IComparable、ICollection和IExtensibleObiect。
如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。接口Index与类型T是协变的,并从一个只读索引器中返回这个类型。
public interface IIndex<out T>
{
T this[int index] { get; }
int Count { get; }
}
如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入
public interface IDisplay<in T>
{
void Show(T item);
}
与类相似, 结构也可以是泛型的。它们非常类似于泛型类,只是没有继承特性。Nullable,它由.NETFramework定义。NETFramework中的一个泛型结构是Nullable。数据库中的数字和编程语言中的数字结构Nullable定义了一个约束:其中的泛型类型T必须是一个结构。把类定义为泛型类型后,就没有低系统开销这个优点了,而且因为类的对象可以为空,所以对类使用Nullable类型是没有意义的。除了Nullable定义的T类型之外,唯一的系统开销是hasValue布尔字段,它确定是设置对应的值,还是使之为空。除此之外,泛型结构还定义了只读属性HasValue和Value,以及一些运算符重载。把Nullable类型强制转换为T类型的运算符重载是显式定义的,因为当hasValue为false时,它会抛出一个异常。强制转换为Nullable类型的运算符重载定义为隐式的,因为它总是能成功地转换.
在泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义。
Swap0方法把T定义为泛型类型,该泛型类型用于两个参数和一个变量temp:
void Swap<T>(ref T x,ref Ty)
{
T temp;
temp=x;
x=y;
y=temp;
}
泛型类型可以用where子句来限制。用于泛型类的这个子句也可以用于泛型方法。
public interface IAccount
{
decimal Balance { get; }
string Name { get; }
}
public class Account : IAccount
{
public string Name { get; }
public decimal Balance { get; }
public Account(string name, Decimal balance)
{
Name = name;
Balance = balance;
}
}
public static class Algorithms
{
public static decimal AccumulateSimple(IEnumerable<Account> source)
{
decimal sum = 0;
foreach (Account a in source)
{
sum += a.Balance;
}
return sum;
}
public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source)
where TAccount : IAccount
{
decimal sum = 0;
foreach (TAccount a in source)
{
sum += a.Balance;
}
return sum;
}
}
如何通过传递一个泛型委托来修改Accumulat()方法。这个Accumulate0方法使用两个泛型参数TI和T2。第一个参数TI用于实现IEnumerable参数的集合,第二个参数使用泛型委托Func
public static T2 Accumulate<T1, T2>(IEnumerable<T1> source, Func<T1, T2, T2> action)
{
// TODO: update to C# 7.1 syntax
T2 sum = default(T2);
foreach (T1 item in source)
{
sum = action(item, sum);
}
return sum;
}
在调用这个方法时,需要指定泛型参数类型,因为编译器不能自动推断出该类型。对于方法的第1个参数
所赋予的accounts集合是IEnumerableAccount>类型。对于第2个参数,使用一个lambda表达式来定义Account
和decimal类型的两个参数,返回一个小数。