Contravariance和Entity Framework 4.0:如何将EntityCollection指定为IEnumerable?

时间:2010-03-03 05:58:12

标签: c# covariance

我已经指定了几个接口,我使用Entity Framework 4实现了这些接口。我能想出的最简单的演示代码是:

public class ConcreteContainer : IContainer
{
    public EntityCollection<ConcreteChild> Children { get; set; }           
}
public class ConcreteChild : IChild
{
}
public interface IContainer
{
    IEnumerable<IChild> Children { get; set; }
}
public interface IChild
{        
}

我从上面收到以下编译器错误:

  

'Demo.ConcreteContainer'可以   没有实现接口成员   'Demo.IContainer.Children'。   'Demo.ConcreteContainer.Children'   无法实施   'Demo.IContainer.Children'   因为它没有匹配   返回类型   'System.Collections.Generic.IEnumerable'

我目前的理解是,这是因为IEnumerable(由EntityCollection实现)是协变的,但可能不是逆变的:

  

此类型参数是协变的。也就是说,你可以使用   您指定的类型或更多的类型   的。有关协方差和逆变的更多信息,   参见泛型中的协方差和逆变。

我是否正确,&amp;如果是这样,有什么方法可以实现我的目标,即纯粹根据其他接口而不是使用具体类来指定IContainer接口?

或者,我是否误解了一些更基本的东西?

3 个答案:

答案 0 :(得分:5)

.NET 4中的泛型差异在这里无关紧要。接口的实现必须在类型方面与接口签名完全匹配

例如,选择ICloneable,如下所示:

public interface ICloneable
{
    object Clone();
}

能够像这样实现它很好

public class Banana : ICloneable
{
    public Banana Clone() // Fails: this doesn't implement the interface
    {
        ...
    }
}

...但.NET不允许这样做。您有时可以使用显式接口实现解决此问题,如下所示:

public class Banana : ICloneable
{
    public Banana Clone()
    {
        ...
    }

    object ICloneable.Clone()
    {
        return Clone(); // Delegate to the more strongly-typed method
    }
}

然而,在你的情况下,你不能这样做。请考虑以下代码,如果ConcreteContainer被视为实现IContainer

,则该代码有效
IContainer foo = new ConcreteContainer();
foo.Children = new List<IChild>();

现在,您的属性设置器实际上只声明与EntityCollection<ConcreteChild>一起使用,因此它显然无法与任何 IEnumerable<IChild>一起使用 - 违反了界面。

答案 1 :(得分:4)

据我所知,你必须实现一个界面 - 你不能假设一个协变/反变体成员将被挑选作为替代。 即使这是允许的,请注意儿童的制定者是一个问题。因为它允许使用任何其他类型的值(例如EntityCollection<ConcreteChild>List<ConcreteChild>)设置 EntityCollection<ConcreteChild2> 类型的属性,因为它们都在实现IEnumerable<IChild>

在当前的设计中,我将在ConcreteContainer中私下实现IContainer,并检查IEnumerable.Children setter中的输入值是否为兼容类型。接近这种设计的另一种方法是使用通用接口,例如:

public interface IContainer<T> where T:IChild
{
    IEnumerable<T> Children { get; set; }
}

答案 2 :(得分:0)

所以你需要实现这个界面,对吗?

public interface IContainer
{
    IEnumerable<IChild> Children { get; set; }
}

但是在真正的类中,您希望属性为EntityCollection<ConcreteChild>类型。这是你如何做到这一点:

public class ConcreteContainer : IContainer
{
    // This is the property that will be seen by code that accesses
    // this instance through a variable of this type (ConcreteContainer)
    public EntityCollection<ConcreteChild> Children { get; set; }           

    // This is the property that will be used by code that accesses
    // this instance through a variable of the type IContainer
    IEnumerable<ConcreteChild> IContainer.Children {
        get { return Children; }
        set {
            var newCollection = new EntityCollection<ConcreteChild>();
            foreach (var item in value)
                newCollection.Add(item);
            Children = newCollection;
        }
    }
}