Contravariance и Entity Framework 4.0: как указать EntityCollection как IEnumerable? - PullRequest
6 голосов
/ 03 марта 2010

Я указал пару интерфейсов, которые я реализую как сущности, используя 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 исключительно в терминах других интерфейсов, а не используя конкретные классы?

Или я неправильно понимаю что-то более фундаментальное?

Ответы [ 3 ]

5 голосов
/ 22 августа 2010

Общая дисперсия в .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> - в нарушение интерфейса.

4 голосов
/ 18 августа 2010

Насколько я понимаю, вы должны реализовать интерфейс - вы не можете предполагать, что ковариантный / противоположный вариант будет выбран в качестве замены. Даже если это было допустимо, обратите внимание, что сеттер для детей является проблемой. Потому что это позволит установить свойство типа EntityCollection<ConcreteChild> со значением любого другого типа, например List<ConcreteChild> или EntityCollection<ConcreteChild2>, потому что оба реализуют IEnumerable<IChild>.

В текущем проекте я буду реализовывать IContainer для частного использования в ConcreteContainer и проверять входное значение в установщике IEnumerable.Children для совместимого типа. Еще один способ приблизиться к этому дизайну - использовать универсальные интерфейсы, такие как:

public interface IContainer<T> where T:IChild
{
    IEnumerable<T> Children { get; set; }
}
0 голосов
/ 23 августа 2010

Так что вам нужно реализовать этот интерфейс, верно?

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