泛型接口、泛型委托、泛型方法、泛型约束

泛型接口

  没有泛型接口,每次试图使用一个非泛型接口(如IComparable)来操纵一个值类型时,都会进行装箱,而且会丢失编译时的类型安全性。这会严重限制泛型类型的应用。所以,CLR提供了对泛型接口的支持。一个引用类型或值类型为了实现一个泛型接口,可以具体指定类型实参;另外,一个类型也可以保持类型实参的未指定状态来实现一个泛型接口。来看一些例子:

  以下泛型接口定义是作为FCL的一部分发布的:

public interface IEnumerable<T> : IDisposable, IEnumerator {
T Current { get; }
}
  下面这个示例实现了上述泛型接口,且指定了类型实参:

internal sealed class Triangle : IEnumerator<Point> {
private Point[] m_vertices;
Point Current { get {...} }
}
  下例实现了相同的泛型接口,但保持类型实参的未指定状态:

internal sealed class ArrayEnumerator<T> : IEnumerator<T> {
private T[] m_array;
//IEnumerator<T>的Current属性是T类型
T Current { get {...} }
}
  注意,一个ArrayEnumerator对象可以枚举一系列T对象(其中的T没有指定,允许代码使用泛型ArrayEnumerator类型在以后为T指定一个具体的类型)。

泛型委托

  CLR支持泛型委托,目的是保证任何类型的对象都能以一种类型安全的方式传给一个回调方法。此外,泛型委托允许一个值类型实例在传给一个回调方法时不执行任何装箱处理。所谓委托,实际是提供了4个方法的一个类定义:一个构造器、一个Invoke方法、一个BeginInvoke方法和一个EndInvoke方法。如果定义的一个委托类型指定了类型参数,编译器会定义好委托类的方法。

  假定像下面这样定义了一个泛型委托:

public delegate TReturn CallMe<TReturn, TKey, TValue> {TKey key, TValue value);
  编译器会将它转换成一个类,该类在逻辑上可以像下面这样表达:

public sealed class CallMe<TReturn, TKey, TValue> : MulticastDelegate
{
public CallMe(Object object, IntPtr method);
public TReturn Invoke(TKey key, TValue value);
public IAsyncResult BeginInvoke(TKey key, TValue value, AsyncCallback callback, Object object);
public TReturn EndInvoke(IAsyncResult result);
}
  FCL配套提供了许多泛型委托类型,其中大多数都用于集合处理。下面是一些例子:

//通常用于操作一个集合项
public delegate void Action<T>(T obj);

//通常用于比较两个集合项,以进行排序
public delegate Int32 Comparison<T>(T x, T y);

//通常用于将集合项从一种类型转换成另一种类型
public delegate TOutput Converter<TInput, TOutput>(TInput input);
  FCL还提供了用于事件的一个泛型委托。下面展示了这个委托:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
   where TEventArgs : EventArgs;
泛型方法

  定义一个泛型引用类型、值类型或接口时,这些类型中定义的任何方法都可以引用类型指定的一个类型参数。类型参数可作为方法的参数,作为方法的返回值,或作为方法内部定义的一个局部变量来使用。然而,CLR还允许一个方法指定它独有的类型参数。这些类型参数可用于参数、返回值或局部变量。如下所示:

internal sealed class GenericType<T>
{
private T m_value;

public GenericType(T value) { m_value = value; }

public TOutput Converter<TOutput>()
{
TOutput result = (TOutput)Convert.ChangeType(m_value, typeof(TOutput));
return reault;
}
}
  为接受out和ref参数的方法使用泛型类型,将非常有趣,因为作为一个out/ref实参传递的变量必须具有与方法参数相同的类型,以避免潜在的对类型安全性的破坏。事实上,Interlocked类的Exchange和CompareExchange方法正是考虑到这个原因而提供了泛型重载版本:
public static class Interlocked
{
public static T Exchange<T>(ref T location, T value) where T : class;
public static T CompareExchange<T>(ref T location, T value, T comparand) where T : class;
}
泛型方法和类型接口

  C#泛型语法因为涉及大量小于和大于符号,影响到代码的可读性。为增强代码创建、可读性和可维护性,C#编译器支持在调用一个泛型方法时进行“类型推导”(type inference)。也就是说,编译器在调用一个泛型方法的时候自动判断(或推导)要使用的类型。如以下代码:

private static void Swap<T>(ref T o1, ref o2)
{
T temp = o1;
o1 = o2;
o2 = temp;
}

private static void CallingSwapUsingInference()
{
int n1 = 1, n2 = 2;
Swap(ref n1, ref n2); //调用Swap<int>

string s1 = "a";
object s2 = "b";
Swap(ref s1, ref s2); //错误,不能推导类型
}
  一个类型可以定义多个方法,让其中一个方法接受一个具体的数据类型,让另一个方法接受一个泛型参数,如下所示:

private static void Display(string s)
{
Console.WriteLine(s);
}

private static void Display<T>(T o)
{
Display(o.ToString()); //调用Display(string)
}
  下面展示Display方法的一些调用方式:

Display("jeff"); //调用Display(string)
Display(123); //调用Display<T>(T)
Display<string>("ai"); //调用Display<T>(T)
  在C#编译器看来,一个更显式的匹配总是优先于一个泛型匹配的。所以,Display("jeff")生成了对非泛型的Display方法的调用。对Display的第三个调用明确指定了一个泛型类型实参string,这告诉编译器不要尝试去推导类型实参,直接使用指定的类型实参。

泛型和其他成员

  在C#中,属性、索引器、事件、操作符方法、构造器和终结器(finalizer)本身不能有类型参数。然而,它们能在一个泛型类型中定义,而且这些成员中的代码能使用类型的类型参数。

  C#之所以不允许这些成员指定它们自己的泛型类型参数,是因为MS的C#开发团队认为其他开发人员很少需要将这些成员作为泛型使用。此外,为这些成员添加泛型支持的代码是相当高的,因为必须为语言设计足够的语法。

可验证证性和限制

  我们可以利用“约束”来限制能指定成泛型实参类型的类型变量。通过限制类型数量,我们可以对那些类型执行更多的操作。以下是一个

Min方法,它指定了一个约束:

public static T Min<T>(T o1, T o2) where T : IComparable<T>
{
if (o1.CompareTo(o2) < 0) return o1;
return o2;
}
  where关键字告诉编译器为T指定的任何类型都必须实现同一个类型(T)的泛型IComparable接口。现在,当代码引用一个泛型类型或方法时,编译器要负责保证类型实参符合指定的约束。如:

private static void CallMin()
{
object o1 = "jeff", o2 = "richter";
object oMin = Min<Object>(o1, o2); //Error
}
  约束可应用于一个泛型类型的类型参数,或应用于一个泛型方法的类型参数。CLR允许基于类型参数名称或约束来进行重载;只能基于arity来重载类型或方法。示例如下:
//可定义以下类型
internal sealed class AType {}
internal sealed class AType<T> {};
internal sealed class AType<T1, T2> {}

//error: 与没有约束的AType<T>冲突
internal sealed class AType<T> where T : IComparable<T> {}

//error:与AType<T1, T2>冲突
internal sealed class AType<T3, T4> {}

internal sealed class AnotherType
{
//可定义以下方法
private static void M() {}
private static void M<T>() {}
private static void M<T1, T2> () {}

//error:与没有约束的M<T>冲突
private static void M<T> where T : IComparable<T> {}

//error:与M<T1, T2>冲突
private static void M<T3, T4> {}
}
  重写一个virtual泛型方法时,重写过的版本必须指定相同数量的类型参数,且这些类型参数会继承由基类的方法在它们上面指定的约束。事实上,重写的方法根本不准许在它的类型参数上指定任何约束。然而,类型参数的名称是可以改变的。

internal class Base
{
public virtual void M<T1, T2>()
where T1 : struct
where T2 : class {}
}

internal sealed class Derived : Base
{
public override void M<T3, T4>()
where T3 : EventArgs //Error
where T4 : class //Error
{}
}
主要约束

  类型参数可以指定零个或一个主要约束。主要约束可以是一个引用类型,它标识了一个没有密封的类。不需要指定以下特殊的引用类型:System.Object、System.Array、System.Delegate、System.MulticastDelegate、System.ValueType、System.Enum或System.Void。

  指定一个引用类型约束时,相当于向编译器承诺:一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的一个类型。如以下示例:

internal sealed class PrimaryConstraintOfStream<T> where T : Stream
{
public void M(T stream)
{
stream.Close();
}
}
  有两个特殊的主要约束:class和struct。其中,class约束向编译器承诺一个指定的类型实参是引用类型。任何类类型、接口类型、委托类型或数组类型都满足这个约束。struct约束向编译器承诺一个指定的类型实参是值类型,包括枚举在内的任何值类型都满足这个约束。但编译器和CLR将任何System.Nullable<T>值类型视为一个特殊类型,而可空的类型不能指定这个约束。原因是Nullable<T>类型将它的类型参数限制为struct,而CLR希望禁止像Nullable<Nullable<T>>这样的一个递归类型。

次要约束

  一个类型参数可以指定零个或多个次要约束,次要约束代表的是一个接口类型。由于能指定多个接口约束,所以为类型实参指定的类型必须实现所有接口约束。

  还有一种辅助约束称为“类型参数约束”,有时也称为“裸类型约束”。这种约束的使用比接口约束少得多。它允许一个泛型类型或方法规定在指定的类型实参之间必须存在一个关系。一个类型参数可以应用零个或多个类型参数约束。下面的示例演示了如何使用类型参数约束:

private static List<TBase> ConvertIList<T, TBase>(IList<T> list) where T : TBase
{
List<Base> baseList = new List<TBase>(list.Count);
for(int index = 0; index < list.Count; index++)
{
baseList.Add(list[index]);
}
return baseList;
}
  该方法指定了两个类型参数,其中T参数由TBase类型参数约束。也就是说不管为T指定什么类型实参,这个类型都必须兼容于为TBase指定的类型实参。下面的方法展示了对ConvertIList的合法和非法调用:

private static void CallingConvertIList()
{
//构造并初始化一个List<string>(它实现了IList<string>)
IList<string> ls = new List<string>();
ls.Add("A String");

//将IList<string>转换为一个IList<Object>
IList<object> lo = ConvertIList<string, object>(ls);

//将IList<string>转换为一个IList<IComparable>
IList<IComparable lc = ConvertIList<string, IComparable>(ls);

//将IList<string>转换为一个IList<IComparable<string>>
IList<IComparable<string>> lcs = ConvertIList<string, IComparable<string>>(ls);

//将IList<String>转换成一个IList<string>
IList<string> ls2 = ConvertIList<string, string>(ls);

//错误:将IList<string>转换成一个IList<Exception>
IList<Exception> le = ConvertIList<string, Exception>(ls);
}
构造器约束

  一个类型参数可以指定零个或一个构造器约束。指定构造器约束相当于向编译器承诺:一个指定的类型实参是实现了一个public无参构造器的一个非抽象类型。注意,如果同时指定了构造器约束和struct约束,C#编译器会认为这是一个错误,因为这是多余的:所有值类型都隐式提供了一个public无参构造器。

  下例就使用了构造器约束来限制它的类型参数:

internal sealed class ConstructorConstraint<T> where T : new()
{
public static T Factory()
{
return new T();
}
}
泛型类型变量的转型

  将一个泛型类型变量转型为另一个类型是非法的,除非将其转换为与一个约束兼容的类型:

private static void CastingAGenericTypeVariable<T>(T obj)
{
int x = (int)obj; //错误
string s = (string)obj; //错误

int x2 = (int)(object)obj; //无错误
string s2 = (string)(object)obj; //无错误

string s3 = obj as string; //无错误
}
将一个泛型类型变量设为默认值

  将泛型类型变量设为null是非法的,除非将泛型类型约束成一个引用类型。

  我们可以使用default关键字将一个引用类型设为null,或将一个值类型设为0。如下所示:

private static void SetDefaultValue<T>()
{
T temp = default(T);
}
  T如果为引用类型,temp的值为null;T如果为值类型,temp的值为0。

将一个泛型类型变量与null进行比较

  无论泛型类型是否被约束,使用==或!=操作符将一个泛型变量与null进行比较都是合法的:

private static void ComparingWithNull<T>(T obj)
{
if(obj == null)
{
//对于值类型来说,此处永远不会执行。
}
}
  如果调用这个方法,并为类型参数传递一个值类型,那么JIT编译器知道if语句永远都不会为true,所以不会为if测试或都大括号内的代码生成本地代码。如果换用!=操作符,JIT编译器不会为if测试生成代码(因为它肯定为true),但会为if大括号内的代码生成本地代码。

  另外,如果T被约束成一个struct,C#编译器会报错。

两个泛型类型变量的相互比较

  如果泛型类型参数不是一个引用类型,那么对同一个泛型类型的两个变量进行比较是非法的。

泛型类型变量作为操作数使用

  将操作符应用于泛型类型的操作数存在大量问题。编译器在编译时无法确定类型,这意味着不能将任何操作符应用于泛型类型的变量。
#asp.net

  

泛型接口、泛型委托、泛型方法、泛型约束

时间: 2024-10-11 22:53:59

泛型接口、泛型委托、泛型方法、泛型约束的相关文章

C#高级编程三十天----泛型结构,泛型方法,泛型委托

泛型结构 泛型结构和泛型类几乎是一直的,只是泛型结构没有继承的特性..NET平台提供的一个泛型结构是(可空类型)Nullablle<T>.可空类型的引入,主要是为了解决数据库语言中的数字与编程语言中的数字的区别(数据库中数字可以为空,编程语言中数字不可为空).因为Nullable<T>使用过于的繁琐,于是就引入了一种特殊的语法,使用个"?"运算符.例: int? x1; Nullable<int> x2; x1和x2这两种方式定义是等价的. 非空类型

c#泛型使用详解:泛型特点、泛型继承、泛型接口、泛型委托

泛型:通过参数化类型来实现在同一份代码上操作多种数据类型.利用"参数化类型"将类型抽象化,从而实现灵活的复用.在.NET类库中处处都可以看到泛型的身影,尤其是数组和集合中,泛型的存在也大大提高了程序员的开发效率.更重要的是,C#的泛型比C++的模板使用更加安全,并且通过避免装箱和拆箱操作来达到性能提升的目的.因此,我们很有必要掌握并善用这个强大的语言特性. C#泛型特点: 1.如果实例化泛型类型的参数相同,那么JIT编辑器会重复使用该类型,因此C#的动态泛型能力避免了C++静态模板可能

泛型委托学习进程

首先先回顾委托的使用过程步骤: 委托使用总结: (1)     委托声明(定义一个函数原型:返回值+参数类型和个数)注:在类的外部--中介(房产中介商) (2)     根据委托定义"具体"的方法------房源   注:在类中定义方法 (3)     创建委托对象,关联"具体方法"---中介商拥有房源  注意:在主函数中操作 第一种方式:使用new初始化.第二种方式:直接给委托变量赋值方法 (4)     通过委托去调用方法(而不是直接调用方法)------中介带

C#规范整理&#183;泛型委托事件

基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用.同时,它减少了泛型类及泛型方法中的转型,确保了类型安全.委托本身是一种引用类型,它保存的也是托管堆中对象的引用,只不过这个引用比较特殊,它是对方法的引用.事件本身也是委托,它是委托组,C#中提供了关键字event来对事件进行特别区分.   一旦我们开始编写稍微复杂的C#代码,就肯定离不开泛型.委托和事件. 1.总是优先考虑泛型 泛型的优点是多方面的,无论是泛型类还是泛型方法都同时具备可重用性.类型安全和高效率等特性,这都是非泛型类和非泛

泛型中的类型约束和类型推断

前一篇文章介绍了泛型的基本概念.在本文中,我们看一下泛型中两个很重要的特性:类型约束和类型推断. 类型约束 相信你还记得前面一篇文章中的泛型方法,在这个泛型方法中,我们就使用了类型约束. 类型约束(type constraint)进一步控制了可指定的类型实参,当我们创建自己的泛型类型或者泛型方法的时候,类型约束是很有用的. 回到前一篇例子中的泛型方法,这个泛型方法就要求可指定的类型实参必须实现了IComparable接口. 为什么会有这个约束呢?原因很简单,因为我们在泛型方法的实现中直接调用T类

泛型委托、Lambda、EF

托管和非托管 运行在CLR上面的就是托管 非托管就是像C.C++编译成一个exe文件直接交给了操作系统的内核执行 2)Lambda 本质就是一个匿名函数 =>:Lambda必须是这个符号 3)泛型委托 方法上面的泛型约束,一般用来约束返回值和传入参数     //泛型委托     public delegate T2 ArryDel<T1,T2>(T1 A,T1 B);     //泛型委托             ArryDel<int, bool> arry = new

c#系统泛型委托

Action<T> 无返回值的系统泛型委托 namespace ConsoleApp1 { public class UserInfo { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } class Program { private static List<UserInfo> getInit() { return new List<User

泛型委托当参数传递

假如有一个Person类: public class Person { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Title { get; set; } } 执行一个方法: /// <summary> /// 传递一个泛型委托方法 /// </summary> /// <param name="acti

泛型委托

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 泛型委托 { public delegate int DelCompare<T>(T t1, T t2); // public delegate int DelCompare(object o1, object o2); class Progra