C# 类和结构作为协变类型参数存在差异
如果在接口中用作协变类型参数的类型是C# 类和结构作为协变类型参数存在差异,c#,generics,covariance,C#,Generics,Covariance,如果在接口中用作协变类型参数的类型是结构,则以下测试失败(在最后一次断言中),但如果是类,则成功 接口IOuter{} 接口IOuter:IOuter{T值{get;} 接口IInner{} 结构内部:IInner{} 类外部:IOuter{public internal Value{get{return new internal();}}} [TestMethod()] 公共无效抵销测试() { var a=新的外部(); Assert.IsTrue(a在外部); //如果内部是结构,则在此失
结构
,则以下测试失败(在最后一次断言中),但如果是类
,则成功
接口IOuter{}
接口IOuter:IOuter{T值{get;}
接口IInner{}
结构内部:IInner{}
类外部:IOuter{public internal Value{get{return new internal();}}}
[TestMethod()]
公共无效抵销测试()
{
var a=新的外部();
Assert.IsTrue(a在外部);
//如果内部是结构,则在此失败。如果内部是类,则成功。
Assert.IsTrue(a在外部);
}
为什么结构和类之间有区别?引用以下内容,这种行为是:
仅当类型参数是引用类型时,才支持差异
因为结构是按值计算的。 在没有装箱操作的情况下,不能将结构“强制”到另一个对象(接口)
“out”只是关于强制转换:它允许您将
IEnumerable
强制转换为IEnumerable
(您将使用不同的类型枚举相同的对象,而不需要任何成本)。。。但这对结构来说毫无意义(需要装箱)。用外行的话说,因为将引用类型视为其他类型(祖先或后代)只需要编译器更新其内部簿记结构;因为所有引用类型的内存中表示具有相同的结构(在standardese中,这涉及隐式引用转换),所以在运行时无需更改任何内容
另一方面,值类型具有(可能)不同的内存表示,因此将值类型A的实例视为值类型B的实例必然涉及运行时转换。If class
FooClass
和structFooStruct
都实现了IFoo
,类型为FooClass
的变量是对IFoo
实现的引用,但类型为FooStruct
的变量本身是IFoo
的实现。参考类型可能存在协方差的原因是,如果T
源自U
,则对T
的每个参考都将是对aU
的参考;如果一个对T
的引用被传递给了一个希望引用U
的方法,那么接收到的参数将是对U
的引用,并且该方法不必关心它也是对T
的引用
协方差不适用于结构类型的原因是Int32
类型的值不是对实现IComparable
的堆对象的引用——它是IComparable
的实现。参数类型为IComparable
的方法不希望收到IComparable
的实现——它希望收到一个引用
注意,一些语言试图假装给定声明
int32v1;对象v2=v1
v1的类型和v2所引用对象的类型是相同的。事实上,它们是居住在不同宇宙中的不同类型。每当运行时环境看到从System.ValueType
派生的System.Enum
以外的类时,它都会在存储位置类型(与堆类型分开)的宇宙中有效地定义第二种类型。如果说icomparable v3=v1代码>,我们正在做的是要求系统创建一个堆对象类型Int32
的实例,其内容从v1
加载,并将对该对象的引用存储到v3
中。尽管系统允许从结构类型隐式转换到相应的堆对象类型,并允许以另一种方式显式转换,但这并不意味着变量和堆对象是同一类型。事实上,需要转换这一事实意味着它们不需要转换。常见问题解答清楚地表明两者之间存在差异,但它实际上并没有回答您提出的问题:为什么?所以问题是,当使用结构类型参数时,当CLR被强制转换为基参数类型时,它需要将嵌套在泛型接口结果中的所有值装箱?因此,对于它来说,编译器给出错误是有意义的,除非类型参数被强制为引用类型(声明,其中T:class
),重新考虑后,如果声明它是一个结构
(比该问题中提出的错误更严重),则这种错误根本不允许将类型与值类型一起使用,而我们只是想防止它们与值类型一起协变使用。@sinelaw:给定一个类似于Foo的声明,其中T:U
,如果U
是任何密封类型(包括结构类型),则意味着T
必须是同一类型。如果U
是另一种类型,那么T
可能会有所不同,但如果U
是一种密封类型或结构,则被困在U
中,这一事实并不意味着不能为U使用结构。这仅仅意味着Foo
在U
是一种结构时可能没有类时那么通用。同意,正如您所暗示的,主要的一点是在您访问这些项时,隐式地转换这些项。