C#泛型与委托中的协变out与逆变in简记

引子

在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关键字。
逆变使你使用的类型可以比泛型参数指定的类型派生程度更小。 这样可以隐式转换实现协变接口的类以及隐式转换委托类型。 引用类型支持泛型类型参数中的协变和逆变,但值类型不支持它们。
仅在类型定义方法参数的类型,而不是方法返回类型时,类型可以在泛型接口或委托中声明为逆变。 Inrefout参数必须是固定的,这意味着它们既不是协变的,也不是逆变的。
具有逆变类型参数的接口使其方法接受的参数的类型可以比接口类型参数指定的类型派生程度更小。 例如,在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];  

参考链接

  1. http://t.zoukankan.com/ningxinjie-p-12674886
  2. https://www.shuzhiduo.com/A/B0zqbKYQJv/
  3. .Net Microsoft官网:out(泛型修饰符)(C# 参考)
  4. .Net Microsoft官网:in(泛型修饰符)(C# 参考)
  5. .Net Microsoft官网:协变和逆变 (C#)
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Kotlin是一种类量的机制,它允许我们在不确定具体类的情况下编写通用代码。Kotlin支持,以及in和out的关键字。 首先,我们来看一个简单的例子,实现一个容器类: ```kotlin class Container<T>(var item: T) { fun getItem(): T { return item } } fun main() { val container = Container<String>("Hello") println(container.getItem()) } ``` 在这个例子,我们定义了一个名为Container的类,它有一个类参数T。我们可以创建一个Container实例,并将其实例化为一个具体类。我们使用getItem方法来获取这个容器的item。 接下来,我们来介绍一下。假设我们有两个类: ```kotlin open class Animal { fun makeSound() { println("Making animal sound") } } class Cat: Animal() { fun meow() { println("Meow") } } ``` 我们可以通过一个简单的示例来说明: ```kotlin fun main() { val animals: List<Animal> = listOf(Cat(), Animal()) makeSounds(animals) } fun makeSounds(animals: List<Animal>) { for (animal in animals) { animal.makeSound() } } ``` 在这个例子,我们定义了一个List<Animal>类量animals,它包含了一个Cat和一个Animal实例。我们将这个量传递给了makeSounds函数,该函数接受一个List<Animal>类的参数。 在makeSounds函数,我们使用for循环遍历animals列表,并对其的每个Animal实例调用makeSound方法。由于Cat是Animal的子类,因此它也可以被视为Animal类,因此我们可以将其添加到List<Animal>类。 这里的List<Animal>类就是的,因为我们可以将它的子类(如Cat)作为参数传递给makeSounds函数。 现在我们来看一下。假设我们有一个接受Animal类的参数的函数: ```kotlin fun takeAnimal(animal: Animal) { animal.makeSound() } ``` 我们可以将这个函数传递给另一个函数,该函数期望一个Cat类的参数。在这种情况下,我们可以使用in关键字来表示: ```kotlin fun main() { val cat: Cat = Cat() takeCat(cat) } fun takeCat(cat: Cat) { takeAnimal(cat) } fun takeAnimal(animal: Animal) { animal.makeSound() } ``` 在这个例子,我们定义了一个takeCat函数,它接受一个Cat类的参数。我们将这个函数传递给了takeAnimal函数,该函数期望一个Animal类的参数。由于Cat是Animal的子类,因此我们可以将Cat类的参数传递给takeAnimal函数。这里的takeAnimal函数是的,因为它可以接受其超类(如Animal)的参数。 最后,我们来看一下out和in关键字。我们可以在定义参数时使用这些关键字来限制参数的使用方式。out关键字用于声明参数是的,in关键字用于声明参数是的。 例如,我们可以定义一个只允许读取的接口: ```kotlin interface ReadOnlyContainer<out T> { fun getItem(): T } ``` 在这个例子,我们使用out关键字来声明参数T是的。这意味着我们只能从ReadOnlyContainer接口获取T类的值,而不能修改它。这样做的好处是可以使我们更加安全地使用参数。 类似地,我们可以定义一个只允许写入的接口: ```kotlin interface WriteOnlyContainer<in T> { fun setItem(item: T) } ``` 在这个例子,我们使用in关键字来声明参数T是的。这意味着我们只能向WriteOnlyContainer接口设置T类的值,而不能获取它。这样做的好处是可以避免意外修改参数的值。 总结一下,Kotlin支持,以及in和out关键字。使用可以使我们更加灵活地使用参数,而使用in和out关键字可以帮助我们更加安全地使用参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天富儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值