C#学习——6.泛型接口中的协变和抗变

1.变体的由来

众所周知,多态性允许把派生类的对象放在基类的变量中,例如:

Cow myCow = new Cow("牦牛");
Animal myAnimal = myCow;

之所以Cow的类型放在Aniaml类型的变量中可行,是因为Cow派生自Animal。但是,在接口中,这种情况是不适用的,会报错。例如下面的代码:

interface IGetObject<T>
{
   T Dosomething(T value);
}

public class MyClass : IGetObject<string>
{
    public string Dosomething(string value)
    {
       return value;
    }
}

static void Main(string[] args)
{
    IGetObject<string> myClassStr= new MyClass();
    IGetObject<object> myClassObj = myClassStr;
    Console.WriteLine("ok");
    Console.ReadKey();
}

由于MyClass支持IGetObject接口,则主函数第一行代码没有问题。但是,第二行代码预先假定两个接口类型之间有某种关系,即IGetObject类型能够隐式转换为IGetObject类型,实际上这种关系不存在,所以无法进行相应的隐式类型转换,程序会报错,因为我们知道泛型类型的所有类型参数都是不变的。那么怎么解决这个问题呢?对,就是通过变体类型。
如果将IGetObject接口类型的参数T定义为协变类型,IGetObject与IGetObject类型之间就可以建立继承关系,则一种类型的变量就可以包含另外一种类型的值,这与多态性有点类似,但比之更为复杂。
抗变与协变是类似的,但方向相反。抗变不能像协变那样,把泛型接口值放在使用基类型的变量中,但可以把接口放在使用派生类的接口变量中。

2.协变

要把泛型参数定义为协变,需要在类型定义中使用out关键字。对于接口定义,协变类型参数只能用作方法的返回值或属性get访问器。如下例子:

interface IGetObject<out T>
{
   T Dosomething(object value);
}

public class MyClass : IGetObject<string>
{
    public string Dosomething(object value)
    {
       return value.ToString();
    }
}

在主函数中调用:

IGetObject<string> myClassStr = new MyClass();
IGetObject<object> myClassObj = myClassStr;
string msg = "武汉加油!";
string str = (myClassObj.Dosomething(msg)).ToString();
Console.WriteLine(str);

结果如下:

武汉加油!

可以看出:编译器将string Dosomething(object value) 转换为 object Dosomething(object value),即实现了IGetObject到IGetObject 的类型转换,子——>父的转换。

3.抗变

要把泛型参数定义为抗变,需要在类型定义中使用in关键字。对于接口定义,抗变类型参数只能用作方法参数,不能用作返回类型。如下例子:

//作输入参数
interface IMyinterface<in T>
{
   void Dosomething(object value);
}
public class MyClass : IMyinterface<object >
{
   public void  Dosomething(object value)
   {
      Console.WriteLine(value.GetType().ToString());
   }
}

在主函数中调用:

IMyinterface<object> myClass1Obj = new MyClass1();
IMyinterface<string> myClass2Str = myClass1Obj;
List<string> list = new List<string>();
myClass2Str.Dosomething(list);

结果如下:

System.Collections.Generic.List`1[System.String]

可以看出:编译器将void Dosomething(object value) 转换为 void Dosomething(string value) ,即实现了IMyinterface到IMyinterface的类型转换,父——>子的转换。

3.Net Framework中的协变与抗变

占坑,有时间再整理。

4.参考资料

欢迎关注我的公众号。
在这里插入图片描述

### 与多态的区别及联系 #### 1. **的定义** 是一种类系统特性,指的是子类的实例也可以被视为其父类的实例。在编程中,表示容器或接口可以接受更具体的类作为参数。例如,在 Java 中,`List<? extends Number>` 是的,因为它允许存储 `Number` 类及其子类(如 `Integer` 或 `Double`)的对象[^2]。 #### 2. **多态的定义** 多态是指同一操作作用于不同对象时表现出不同的行为。它分为两种主要形式: - **编译时多态**:通过方法重载运算符重载实现。 - **运行时多态**:通过虚函数(C++)、动态绑定(Java/C# 等语言)等方式实现。运行时多态依赖于继承虚函数/接口机制,使程序能够在运行时根据实际对象类调用相应的方法[^1]。 #### 3. **与多态的主要区别** | 特性 | | 多态 | |-----------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| | **核心概念** | 子类的实例可视为父类的实例,主要用于描述类之间的兼容性转换关系 | 同一操作在不同对象上有不同的表现形式 | | **适用场景** | 主要用于、数组、委托等涉及类安全性的上下文中 | 应用于面向对象编程中的继承体系结构 | | **实现方式** | 通过类系统的设计(如 Java 的通配符 `? extends` `? super`) | 通过虚函数、接口、动态绑定等技术 | | **时间特征** | 编译期检查 | 运行时动态绑定 | #### 4. **与多态的联系** 尽管两者关注的重点不同,但在某些高级编程范式中,它们之间存在一定的交集: - **与多态结合**:在支持的语言中(如 Java),可以通过配合多态实现复杂的类安全设计。例如,`List<? extends Animal>` 不仅能容纳 `Animal` 对象,还能容纳其子类对象(如 `Dog`)。当这些对象被取出并调用方法时,会触发基于实际类的多态行为[^3]。 - **虚拟表的支持**:无论是还是多态,底层都可能依赖于类似的机制——虚拟表(vtable)。虚拟表记录了每个类的虚函数地址,使得即使在的情况下也能正确调用对应的具体实现。 #### 5. **示例代码展示两者的差异与关联** 以下是 C++ 中的一个简单例子,展示了如何将与多态结合起来使用: ```cpp #include <iostream> using namespace std; class Base { public: virtual ~Base() {} // 虚析构函数确保资源释放 virtual void speak() const { cout << "I am base." << endl; } // 基础版本的行为 }; class Derived : public Base { public: void speak() const override { cout << "I am derived." << endl; } // 派生类重新定义行为 }; void makeSpeak(Base* obj) { obj->speak(); // 动态绑定,取决于传入的实际对象类 } int main() { Base* b = new Derived(); makeSpeak(b); // 输出 "I am derived." delete b; return 0; } ``` 在这个例子中: - `Derived` 继承自 `Base` 并重写了虚函数 `speak()`,体现了多态的特点; - 如果我们将 `makeSpeak` 函数扩展为接受某种(假设语言支持),那么还可以进一步增强灵活性。 --- ### 总结 侧重于解决类间的兼容性问题,而多态强调的是同一种操作针对不同对象的表现能力。二者虽独立发展,但在现代编程实践中经常相互作以构建强大且灵活的应用框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

遥感与地理信息

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

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

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

打赏作者

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

抵扣说明:

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

余额充值