由于IEnumerable
在C#4.0中有一个协变参数,我对它在以下代码中的行为方式感到困惑。
public class Test
{
IEnumerable<IFoo> foos;
public void DoTestOne<H>(IEnumerable<H> bars) where H : IFoo
{
foos = bars;
}
public void DoTestTwo(IEnumerable<IBar> bars)
{
foos = bars;
}
}
public interface IFoo
{
}
public interface IBar : IFoo
{
}
所以基本上DoTestOne
方法不会在DoTestTwo
时编译。除了它为什么不起作用之外,如果有人知道如何实现DoTestOne
的效果(将IEnumberable<H> where H : IFoo
分配给IEnumberable<IFoo>
),我将不胜感激。
答案 0 :(得分:9)
如果您知道H将成为一个班级,这确实有效:
public void DoTestOne<H>(IEnumerable<H> bars) where H : class, IFoo
{
foos = bars;
}
这里的问题是,如果H是值类型,协方差并不完全符合您的预期,因为IEnumerable<MyStruct>
实际返回值类型,而IEnumerable<IFoo>
必须返回盒装实例。如有必要,您可以使用明确的Cast<IFoo>
解决此问题。
答案 1 :(得分:3)
你只需要在IEnumerable<IFoo>
进行投射:
public void DoTestOne<H>(IEnumerable<H> bars) where H : IFoo
{
foos = (IEnumerable<IFoo>)bars;
}
编辑由Dan Bryant提供:当foos = bars.Cast<IFoo>()
为H
时,使用struct
代替上述规避InvalidCastException。
答案 2 :(得分:0)
您忘记了回复中的演员表或您的通用约束中的“类”标识符。你当然可以做什么,参考下面。
来自:http://msdn.microsoft.com/en-us/library/d5x73970.aspx
where T : <interface name>
The type argument must be or implement the specified interface.
Multiple interface constraints can be specified. The constraining interface can also be generic.
答案 3 :(得分:0)
在.net运行时中,每个值类型都有一个具有相同名称的关联堆对象类型。在某些情况下,将使用值类型;在其他上下文中,堆类型。当声明值类型的存储位置(变量,参数,返回值,字段或数组槽)时,该存储位置将保存该类型的实际内容。声明类类型的存储位置时,它将保留null
或对存储在其他位置的堆对象的引用。接口类型的存储位置被视为类似于引用类型的存储位置,并且即使接口的某些(或所有)实现实际上是值类型,也保持堆引用。
尝试将值类型存储到引用类型存储位置将导致系统创建与值类型关联的堆类型的新实例,将原始存储位置中的所有字段复制到新实例,并存储对该实例的引用,称为“装箱”的过程。尝试将堆引用强制转换为值类型存储位置将检查它是否引用与值类型关联的堆类型的实例;如果是,则堆对象的字段将被复制(“unboxed”)到值类型存储位置中的相应字段。
虽然看起来像System.Int32
这样的类型来自System.Object
,但这只有一半是正确的。存在堆对象类型System.Int32
,它确实派生自System.Object
,但类型System.Int32
的变量不包含对此类对象的引用。相反,这样的变量保存与该整数相关的实际数据; 数据本身只是一个位集合,并不是来自任何东西。
如果认为接口类型存储位置为“Something派生自System.Object实现接口 _ ”,那么实现该接口的任何类类型的实例都是该类型的实例,但值类型的实例 - 即使它们可以转换为其他类型 - 也不是任何其他类型的实例。使用IEnumerator<IFoo>
的代码不仅希望其Current
方法返回可转换为IFoo
的内容,还是实现IFoo
;它希望它返回 的实现Object
IFoo
的衍生物。因此,要IEnumerable<T>
替换IEnumerable<IFoo>
,T
必须限制实施IFoo
并成为System.Object
的正确衍生物。