引子
在C#中要想将一个泛型对象转换为另一个泛型对象时,必须要将一个泛型对象拆箱,对元素进行显式或隐式转换后重新装箱。
例如:
List<Object> lobject=new List<Object>(){"0","1","2"};
List<int> lint=new List<int>();
foreach(Object obj in lobject)
{
lint.Add((int)obj);
}
在这个拆箱和装箱的过程中,要消耗掉相当于lobject
对象两倍的内存和资源,如果lobject
对象非常大,这将是非常巨大的资源消耗。泛型修饰符就是用来解决这个问题的。
当两个泛型对象的元素存在继承关系是,可以在实现泛型对象是为泛型元素增加in
或者out
声明,以便逆变或协变。
在 C# 中,协变和逆变能够实现数组类型、委托(delegate
)类型和泛型接口(interface
)类型参数的隐式引用转换。 协变保留分配兼容性,逆变则与之相反。
协变out
对于泛型类型参数,out
关键字可指定类型参数是协变的。 可以在泛型接口和委托中使用out
关键字。
协变使你使用的类型可以比泛型参数指定的类型派生程度更大。 这样可以隐式转换实现协变接口的类以及隐式转换委托类型。 引用类型支持协变和逆变,但值类型不支持它们。
具有协变类型参数的接口使其方法返回的类型可以比类型参数指定的类型派生程度更大。 例如,在IEnumerable<T>
中,类型T
是协变的,所以可以将IEnumerable(Of String)
类型的对象分配给IEnumerable(Of Object)
类型的对象,而无需使用任何特殊转换方法。
可以向协变委托分配相同类型的其他委托,不过要使用派生程度更大的泛型类型参数。
被out
修饰的协变类型参数:只能作为返回值,不能作为参数。
协变泛型接口
public interface IListOut<out T>
{
T Get();//T只能作为返回值,不能作为参数
}
public class ListOut<T> : IListOut<T>
{
public T Get() { return default(T); }
}
ListOut<int> lint = new ListOut<int>(){0,1,2};
ListOut<Object> lobject = new ListOut<Object>();
//隐式转换委托类型
lobject = lint;//ListOut<Object> lobject = new ListOut<int>(){0,1,2};
简单理解就是:右边必须是左边的扩展(因为是out
),即右边需要继承或者实现了左边;上方就是右边的int
继承了左边的Object
。
协变委托
public delegate R DCovariant<out R>();
public static Control SampleControl()
{ return new Control(); }
public static Button SampleButton()
{ return new Button(); }
public void Test()
{
DCovariant<Control> dControl = SampleControl;
DCovariant<Button> dButton = SampleButton;
dControl = dButton;//隐式转换委托类型
dControl();
}
在泛型委托中,如果类型仅用作方法返回类型,而不用于方法参数,则可以声明为协变。
逆变in
对于泛型类型参数,in
关键字可指定类型参数是逆变的。 可以在泛型接口和委托中使用in
关键字。
逆变使你使用的类型可以比泛型参数指定的类型派生程度更小。 这样可以隐式转换实现协变接口的类以及隐式转换委托类型。 引用类型支持泛型类型参数中的协变和逆变,但值类型不支持它们。
仅在类型定义方法参数的类型,而不是方法返回类型时,类型可以在泛型接口或委托中声明为逆变。 In
、ref
和out
参数必须是固定的,这意味着它们既不是协变的,也不是逆变的。
具有逆变类型参数的接口使其方法接受的参数的类型可以比接口类型参数指定的类型派生程度更小。 例如,在IComparer<T>
接口中,类型T
是逆变的,可以将IComparer<Person>
类型的对象分配给IComparer<Employee>
类型的对象,而无需使用任何特殊转换方法(如果Employee
继承Person
)。
可以向逆变委托分配相同类型的其他委托,不过要使用派生程度更小的泛型类型参数。
被in
修饰的逆变类型参数:只能作为参数,不能作为返回值。
逆变泛型接口
public interface IListIn<in T>
{
void Show(T t);//T只能作为参数,不能作为返回值
}
public class ListIn<T> : IListIn<T>
{
public void Show(T t) { }
}
ListIn<Object> lobject = new ListIn<Object>(){"0","1","2"};
ListIn<int> lint = new ListIn<int>;
//隐式转换委托类型
lint = lobject;//ListIn<int> lint = new ListIn<Object>(){"0","1","2"};
简单理解就是:右边必须是左边的基类或者实现的接口(因为是in
),即右边需要左边继承或实现了它;上方就是右边的Object
被左边的int
继承了。
逆变委托
public delegate void DContravariant<in A>(A argument);
public static void SampleControl(Control control)
{ }
public static void SampleButton(Button button)
{ }
public void Test()
{
DContravariant<Control> dControl = SampleControl;
DContravariant<Button> dButton = SampleButton;
dButton = dControl;//隐式转换委托类型
dButton(new Button());
}
在泛型委托中,如果类型仅用作方法参数,而不用于方法返回类型,则可以声明为逆变。
数组协变
数组的协变使派生程度更大的类型的数组能够隐式转换为派生程度更小的类型的数组。 但是此操作不是类型安全的操作,如以下代码示例所示。
object[] array = new String[10];