【问题标题】:Contravariance and Entity Framework 4.0: how to specify EntityCollection as IEnumerable?逆变和实体框架 4.0:如何将 EntityCollection 指定为 IEnumerable?
【发布时间】:2010-03-03 05:58:12
【问题描述】:

我已经指定了几个接口,我正在使用 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 实现)是协变的,但可能不是逆变的:

此类型参数是协变的。也就是说,您可以使用 您指定的类型或任何更多的类型 衍生的。有关协变和逆变的更多信息, 请参阅泛型中的协变和逆变。

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

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

【问题讨论】:

    标签: c# covariance


    【解决方案1】:

    .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&lt;ConcreteChild&gt;一起工作,所以它显然不能与anyIEnumerable&lt;IChild&gt;一起工作——违反了接口。

    【讨论】:

      【解决方案2】:

      据我了解,您必须实现一个接口 - 您不能假设会选择一个协变/反变成员作为替代。 即使这是允许的,请注意,儿童的二传手是一个问题。因为它将允许将 EntityCollection&lt;ConcreteChild&gt; 类型的属性设置为任何其他类型的值,例如 List&lt;ConcreteChild&gt;EntityCollection&lt;ConcreteChild2&gt;,因为两者都实现了 IEnumerable&lt;IChild&gt;

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

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

      【讨论】:

      • 感谢您确认我的怀疑。通用接口方法很好,但不适用于 EF4,这使得 EF4 在单元测试时很难完全模拟。因此,我们主要转向 EF 实体的集成测试。
      【解决方案3】:

      所以你需要实现这个接口,对吧?

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

      但在真正的类中,您希望属性为EntityCollection&lt;ConcreteChild&gt; 类型。您可以这样做:

      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;
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-01
        • 2011-02-25
        • 2015-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多