泛型

作者:追风剑情 发布于:2016-6-20 15:56 分类:C#

示例一

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GenericityTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Calculator<int, string> cal = new Calculator<int, string>(120, "aaa");
            cal.SetCallBack(CallBack);

            Console.Read();
        }

        static void CallBack(int i, string s)
        {
            Console.WriteLine("i={0}, s={1}", i, s);
        }
    }

    public class Calculator<T, V>
    {
        public delegate void CallBack(T t, V v);

        T t;
        V v;

        public Calculator(T t, V v)
        {
            this.t = t;
            this.v = v;
        }

        public void SetCallBack(CallBack callback)
        {
            callback(t, v);
        }
    }
}


运行测试

111111.png

 

示例二:  利用where限制泛型类型

using System;

namespace TTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Fun<IClass, IClass>(new ClassA(), new ClassB());

            Console.Read();
        }

        public static void Fun<T, V>(T t, V v)
            where T : IClass
            where V : IClass
        {
            Console.WriteLine("Fun");
        }
    }

    class IClass
    {

    }

    class ClassA : IClass
    {

    }

    class ClassB : IClass
    {

    }
}

 

示例三: 用new()限定泛型必须要有public构造方法

public static void Fun<T, V>(T t, V v)
            where T : IClass, new()
            where V : IClass, new()
{
            Console.WriteLine("Fun");
}


集合向上转型(协变)

IEnumerable<int> collection1 = new List<int>();
IEnumerable<object> collection2 = collection1;

//由于声明了out关键字,所以可实现集合向上转型(协变)
public interface IEnumerable<out T> : IEnumerable
{
	IEnumerator<T> GetEnumerator();
}

泛型(generic)是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”。

CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。此外,CLR还允许创建泛型接口和泛型委托。方法偶尔也封装有用的算法,所以CLR允许在引用类型、值类型或接口中定义泛型方法。

注意  根据Microsoft的设计原则,泛型参数变量要么称为T,要么至少以大写T开头(如TKey和TValue)。大写T代表类型(Type),就像大写I代表接口(Interface)一样,比如IComparable。

注意  应该意识到,首次为特定数据类型调用方法时,CLR都会为这个方法生成本机代码。这会增大应用程序的工作集(working set)大小,从而损害性能。

简化语法

using DateTimeList = System.Collections.Generic.List<System.DateTime>;
Boolean sameType = (typeof(List<DateTime>) == typeof(DateTimeList));//true

实现泛型接口

// 实现泛型接口,并指定类型实参
internal sealed class Triangle : IEnumerator<Point> {
	private Point[] m_vertices;
	
	// IEnumerator<Point>的Current属性是Point类型
	public Point Current { get { ... } }
	...
}

// 实现泛型接口,未指定实参
internal sealed class Triangle : IEnumerator<T< {
	private T[] m_vertices;
	
	// IEnumerator<T>的Current属性是T类型
	public T Current { get { ... } }
	...
}

泛型委托

//定义泛型委托
public delegate TReturn CallMe<TReturn, TKey, TValue>(TKey key, TValue value);

//编译器会将CallMe转换成如下所示的类:
public sealed class CallMe<TReturn, TKey, TValue> : MulticastDelegate {
	public CallMe(Object object, IntPtr method);
	public virtual TReturn Invoke(TKey key, TValue value);
	public virtual IAsyncResult BeginInvoke(TKey key, TValue value,
		AsyncCallback callback, Object object);
	public virtual TReturn EndInvoke(IAsyncResult result);
}

注意  建议尽量使用FCL预定义的泛型Action和Func委托。

委托和接口的逆变和协变泛型类型实参

委托的每个泛型类型参数都可标记为协变量或逆变量。利用这个功能,可将泛型委托类型的变量转换为相同的委托类型(但泛型参数类型不同)。泛型类型参数可以是以下任何一种形式。

不变量(invariant)意味着泛型类型参数不能更改。
逆变量(contravariant)意味着泛型类型参数可以从一个类更改为它的某个派生类。在C#是用in关键字标记逆变量形式的泛型类型参数。逆变量泛型类型参数只出现在输入位置,比如作为方法的参数。
协变量(covariant)意味着泛型类型参数可以从一个类更改为它的某个基类。C#是用out关键字标记协变量形式的泛型类型参数。协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型。

例如,假定存在以下委托类型定义(顺便说一下,它真的存在):
public delegate TResult Func<in T, out TResult>(T arg);
其中,泛型类型参数T用in关键字标记,这使它成为逆变量;泛型类型参数TResult用out关键字标记,这使它成为协变量。

所以,如果像下面这样声明一个变量:
Func<Object, ArgumentException> fn1 = null;
就可将它转型为另一个泛型类型参数不同的 Func 类型:
Func<String, Exception> fn2 = fn1; // 不需要显式转型
Exception e = fn2("");
上述代码的意思是说:fn1变量引用一个方法,获取一个 Object,返回一个ArgumentfException。而 fn2 变量引用另一个方法,获取一个 String,返回一个 Exception,由于可将一个 String 传给期待 Object 的方法(因为 String 从 Object 派生),而且由于可以获取返回ArgumentException的一个方法的结果,并将这个结果当成一个Exception(因为Exception是ArgumentException的基类),所以上述代码能正常编译,而且编译时能维持类型安全性。

注意  只有编译器能验证类型之间存在引用转换,这些可变性才有用换言之,由于需要装箱,所以值类型不具有这种可变性。我个人认为正是因为存在这个限制,这些可变性的用处不是特别大。例如,假定定义以下方法:
void ProcessCollection(IEnumerable<Object> collection){...}

我不能在调用它时传递一个List<DateTime>对象引用,因为DateTime值类型和Object之间不存在引用转换——虽然DateTime是从Object派生的。为了解决这个问题,可像下面这样声明 ProcessCollection:
void ProcessCollection<T>(IEnumerable<T> collection){...}

另外,ProcessCollection(IEnumerable<Object> collection)最大的好处是JIT编译得到的代码只有一个版本。但如果使用ProcessCollection<T>(IEnumerable<T> collection),那么只有在 T 是引用类型的前提下,才可共享同一个版本的 JIT 编译代码。对于T是值类型的情况,每个值类型都有一份不同的 JIT 编译代码;不过,起码能在调用方法时传递一个值类型集合了。

另外,对于泛型类型参数,如果要将该类型的实参传给使用outref关键字的方法,便不允许可变性。例如,以下代码会造成编译器报告错误消息:无效的可变性:类型参数"T"在"SomeDelegate<T>.Invoke(refT)"中必须是不变量。现在的"T"是逆变量。
delegate void SomeDelegate<in T>(ref T t);

使用要获取泛型参数和返回值的委托时,建议尽量为逆变性和协变性指定inout关键字。这样做不会有不良反应,并使你的委托能在更多的情形中使用。

和委托相似,具有泛型类型参数的接口也可将类型参数标记为逆变量和协变量。下面的示例接口有一个逆变量泛型类型参数:

public interface IEnumerator<in T> : IEnumerator {
	Boolean MoveNext();
	T Current { get; }
}

由于T是逆变量,所以以下代码可顺利编译和运行:

// 这个方法接受任意引用类型的一个IEnumerable
Int32 Count(IEnumerable<Object> collection) { ... }
...
// 以下调用向Count传递一个IEnumerable
Int32 c = Count(new [] { "Grant" });

泛型方法

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 result;
	}
}
public static class Interlocked {
	public static T Exchange<T>(ref T location1, T value) where T: class;
	public static T CompareExchange<T>(
		ref T location1, T value, T comparand) where T: class;
}

可验证性和约束

public static T Min<T>(T o1, T o2) where T : IComparable<T> {
	if (o1.CompareTo(o2) < 0) return o1;
	return o2;
}

泛型只能基于元数(类型参数个数)对类型或方法进行重载

// 可以定义以下类型
internal sealed class AType {}
internal sealed class AType<T> {}
internal sealed class AType<T1, T2> {}

// 错误:与没有约束的AType<T>冲突
internal sealed class AType<T> {} where T : IComparable<T> {}
// 错误:与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>() {}
	
	// 错误:与没有约束的M<T>冲突
	private static void M<T>() where T : IComparable<T> {}
	// 错误:与M冲突
	private static void M<T3, T4>()
}

重写虚泛型方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数会继承在基类方法上指定的约束。事实上,根本不允许为重写方法的类型参数指定任何约束。但类型参数的名称是可以改变的。类似地,实现接口方法时,方法必须指定与接口方法等量的类型参数,这些类型参数将继承由接口方法指定的约束。下例使用虚方法演示了这一规则:

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 //错误
		where T4 : class     //错误
	{}
}

主要约束

类型参数可以指定零个或者一个主要约束。主要约束可以是代表非密封类的一个引用类型。不能指定以下特殊引用类型:System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum或者System.Void。

有两个特殊的主要约束:classstruct。class表示引用类型约束(任何类类型、接口类型、委托类型、数组等),struct表示值类型约束(数字、枚举等)。CLR将任何System.Nullable<T>值类型视为特殊类型,不满足这个struct约束。原因是Nullable<T>类型将它的类型参数约束为struct,而CLR希望禁止像Nullable<Nullable<T>>这样的递归类型。

所有值类型都隐式地有一个无参构造器,当T被约束为struct时,new T()是合法的。

// 公共无参构造器约束 new()
internal sealed class ContructorConstraint<T> where T : new() {
	public static T Factory() {
		// 允许,因为所有值类型都隐式有一个公共无参构造器
		// 而如果指定的是引用类型,约束也要求它提供公共无参构造器
		return new T();
	}
}

目前,CLR(以及C#编译器)只支持无参构造器约束。Microsoft认为这已经能满足几乎所有情况。

default关键字设置默认值,例如 T temp = default(T) 如果T是引用类型将返回null,如果T是值类型将返回0。

未做任何约束的泛型类型也可以使用==!=操作符与null进行比较。

不能将基元类型(Byte, Int16, Int32, Int64, Decimal等)操作符(比如+,-,*和/)应用于泛型类型变量。因为编译器在编译时确定不了类型。

标签: C#

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号