Contravariance and Entity Framework 4.0 structure: how to specify EntityCollection as IEnumerable?

I have indicated a couple of interfaces that I implement as objects using the Entity Framework 4. The simplest demo code I can come up with is:

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

I get the following compiler error from the above:

'Demo.ConcreteContainer' does not use the interface 'Demo.IContainer.Children. 'Demo.ConcreteContainer.Children' cannot implement 'Demo.IContainer.Children' because it does not have a matching return type 'System.Collections.Generic.IEnumerable'

Currently, I understand that this is because IEnumerable (which is implemented by EntityCollection) is covariant, but supposedly not contravariant:

This type parameter is covariant. That is, you can use either the type you specify, or any type that is more derived. For more information on covariance and contravariance, see Covariance and contravariance in generics.

Is it right, and if so, is it possible to somehow achieve my goal of defining an IContainer interface only from the point of view of other interfaces, and not using specific classes?

Or do I not understand something more fundamental?

+6
c # covariance
source share
3 answers

The overall variance in .NET 4 does not matter here. The implementation of the interface must match the signature of the interface exactly in terms of types.

For example, take ICloneable , which looks like this:

 public interface ICloneable { object Clone(); } 

It would be nice to implement it like this:

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

... but .NET does not allow this. Sometimes you can use an explicit implementation of an interface implementation, for example:

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

However, in your case, you can never do this. Consider the following code that would be valid if ConcreteContainer considered implemented by IContainer :

 IContainer foo = new ConcreteContainer(); foo.Children = new List<IChild>(); 

Now your property setter is actually declared to work with EntityCollection<ConcreteChild> , so it obviously cannot work with any IEnumerable<IChild> - in violation of the interface.

+5
source share

As far as I understand, you should implement the interface - you cannot assume that the covariant / contravariant member will be chosen as a replacement. Even if this is acceptable, note that the issue with setters for children is a problem. Because it will allow you to set a property of type EntityCollection<ConcreteChild> with a value of any other type, such as List<ConcreteChild> or EntityCollection<ConcreteChild2> , because both implement IEnumerable<IChild> .

In the current project, I will implement IContainer privately in ConcreteContainer and check the input value in the IEnumerable.Children setter for a compatible type. Another way to approach this design is to have common interfaces, such as:

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

So you need to implement this interface, right?

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

But in a real class, you want the property to be of type EntityCollection<ConcreteChild> . Here's how you can do it:

 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; } } } 
0
source share

All Articles