Implementing an ICollection with a con

2019-04-30 19:27发布

问题:

So, this is kind of an obtuse question, but let me see if I can lay it out relatively simply. Lets say I have the following interface:

public interface IFoo
{
    ICollection<IBar> Bars { get; set; }
}

Which I then implement with:

public class Foo : IFoo
{
    public virtual ICollection<IBar> Bars { get; set; }
}

Only Entity Framework can't work with interfaces, so it pretty much completely ignores this navigation property. In order to get EF to recognize it, I need to change it to:

public virtual ICollection<Bar> Bars { get; set; }

Where Bar would be my implementation of IBar. Only, that fails to implement the interface, which wants IBar not Bar.

Now, consider a slightly different scenario, where I've just got a basic foreign key:

public interface IFoo
{
    IBar Bar { get; set; }
}

public class Foo : IFoo
{
    public virtual IBar Bar { get; set; }
}

Same issue, but here, I can solve it by adding:

public class Foo : IFoo
{
    public virtual Bar Bar { get; set; }
    IBar IFoo.Bar
    {
        get { return Bar; }
        set { Bar = (Bar)value; }
    }
}

EF is happy because it has a concrete type and the interface is happy because it has an implementation with IBar. The problem is that I can't figure out how to apply the same logic with an ICollection<IBar> because (ICollection<Bar>)value raises an exception saying "Cannot implicitly convert type ICollection<Bar> to ICollection<IBar>".

How should I properly make the cast?

UPDATE

So, I wasn't paying close enough attention to where the error was being generated. It was actually complaining about the get { return Bars; } bit. I was able to get rid of the error by changing it to:

public class Foo : IFoo
{
    public virtual ICollection<Bar> Bars { get; set; }
    ICollection<IBar> IFoo.Bars
    {
        get { return (ICollection<IBar>)Enumeration.Cast<IBar>(Bars); }
        set { Bars = (ICollection<Bar>)value; }
    }
}

That seems a little hokey to me though, like I'm only masking the error and creating a little time bomb for myself. I'd appreciate any thoughts or alternate solutions.

回答1:

To let the covariance/contravariance work I define navigation properties as enumerables in my interfaces:

public interface IFoo
{
    IEnumerable<IBar> Bars { get; }
}

public class Foo : IFoo
{
    public virtual ICollection<Bar> Bars { get; set; }
    IEnumerable<IBar> IFoo.Bars
    {
       return this.Bars;
    } 
}

This is still enough for EF LINQ queries built on interface types.



回答2:

I use a generic interface adapter like this

public class InterfaceCollectionAdapter<TConcrete, TInterface> : ICollection<TInterface> where TConcrete : TInterface
{
    private Func<ICollection<TConcrete>> _gettor;
    private Action<ICollection<TConcrete>> _settor;
    private Func<ICollection<TConcrete>> _factory;

    private ICollection<TConcrete> Wrapped
    {
        get
        {
            var value = _gettor();

            if (value == null && _settor != null)
            {
                value = (_factory != null)
                    ? _factory()
                    : new List<TConcrete>();

                _settor(value);
            }

            return value;
        }
    }

    public InterfaceCollectionAdapter(Func<ICollection<TConcrete>> gettor, Action<ICollection<TConcrete>> settor = null, Func<ICollection<TConcrete>> factory = null)
    {
        _gettor = gettor;
        _settor = settor;
        _factory = factory;
    }

    public void Add(TInterface item)
    {
        Wrapped.Add((TConcrete)item);
    }

    public void Clear()
    {
        Wrapped.Clear();
    }

    public bool Contains(TInterface item)
    {
        return Wrapped.Contains((TConcrete)item);
    }

    public void CopyTo(TInterface[] array, int arrayIndex)
    {
        var wrapped = Wrapped;
        foreach (var item in wrapped)
        {
            array[arrayIndex++] = (TInterface)item;
        }
    }

    public int Count
    {
        get { return Wrapped.Count; }
    }

    public bool IsReadOnly
    {
        get { return Wrapped.IsReadOnly; }
    }

    public bool Remove(TInterface item)
    {
        return Wrapped.Remove((TConcrete)item);
    }

    public IEnumerator<TInterface> GetEnumerator()
    {
        var wrapped = Wrapped;
        foreach (var item in wrapped)
            yield return item;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Then in the POCO you simply do this

 public Foo()
 {
    public Foo()
    {
        _bars = new InterfaceCollectionAdapter<Bar, IBar>(() => this.Bars, (value) => { this.Bars = value; });
    }

    private InterfaceCollectionAdapter<Bar, IBar> _bars;

    [NotMapped]
    ICollection<IBar> IFoo.Bars
    {
        get
        {
            return _bars;
        }
    }