15

For a scenario such as this:

public interface IAnimal
{

}

public interface IGiraffe : IAnimal
{

}

public interface IQuestionableCollection : IEnumerable<IAnimal>
{
    void SomeAction();
}

public interface IQuestionableCollection<out T> : IQuestionableCollection, IEnumerable<T>
    where T : IAnimal
{

}

public class QuestionableCollection<T> : IQuestionableCollection<T>
    where T:IAnimal
{
    // Implementation... 
}

The complier will generate an error:

'IQuestionableCollection<T>' cannot implement both 'System.Collections.Generic.IEnumerable<IAnimal>' and 'System.Collections.Generic.IEnumerable<T>' because they may unify for some type parameter substitutions

And that makes sense, there is indeed an ambiguity between the two interfaces which C# can't resolve unless it uses the type constraint, which it doesn't per the language spec as @ericlippert explains here.

My question is how should I implement something to the same effect here?

It seems like I should be able to express that the collection is enumerable for the base interface. (I'd like to provide a set of methods that could be utilized without knowing the concrete type, as well as it make some APIs/reflection code cleaner, so I'd like to keep the base collection as non-generic if at all possible. Otherwise, there would be no need for two interfaces.)

The only implementation I can think of that compiles is something like:

public interface IQuestionableCollectionBase
{
    void SomeAction();
}

public interface IQuestionableCollection : IQuestionableCollectionBase, IEnumerable<IAnimal>
{

}

public interface IQuestionableCollection<out T> : IQuestionableCollectionBase, IEnumerable<T>
    where T : IAnimal
{

}

public class QuestionableCollectionBase<T> : IQuestionableCollection
    where T : IAnimal
{
    protected List<T> _items = new List<T>();

    public void SomeAction() { }

    IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_items).GetEnumerator(); }
    IEnumerator<IAnimal> IEnumerable<IAnimal>.GetEnumerator() { return ((IEnumerable<IAnimal>)_items).GetEnumerator(); }
}

public class QuestionableCollection<T> : QuestionableCollectionBase<T>, IQuestionableCollection<T>
    where T : IAnimal
{
    public IEnumerator<T> GetEnumerator() { return ((IEnumerable<T>)_items).GetEnumerator(); }
}

Note that I've had to move any methods I'd like to use on both interfaces to a base method and have two levels of implementation for the class itself - which seems like I'm jumping through enough hoops here that I've got to be missing something...

How should this be implemented?

Community
  • 1
  • 1
Gene
  • 1,587
  • 4
  • 18
  • 38

4 Answers4

5

The simplest workaround is to change the IEnumerables from "is-a" to "has-a", like this:

public interface IAnimal { }
public interface IGiraffe : IAnimal { }

public interface IQuestionableCollection
{
    IEnumerable<IAnimal> Animals { get; }
    void SomeAction();
}

public interface IQuestionableCollection<out T> : IQuestionableCollection
    where T : IAnimal
{
    new IEnumerable<T> Animals { get; }
}

public class QuestionableCollection<T> : IQuestionableCollection<T>
    where T : IAnimal, new()
{
    private readonly List<T> list = new List<T>();

    public IEnumerable<T> Animals
    {
        get { return list; }
    }

    IEnumerable<IAnimal> IQuestionableCollection.Animals
    {
        get { return (IEnumerable<IAnimal>)list; }
    }

    public void SomeAction()
    {
        list.Add(new T());
    }
}

class Giraffe : IGiraffe { }

[TestMethod]
public void test()
{
    var c = new QuestionableCollection<Giraffe>();
    IQuestionableCollection<Giraffe> i = c;
    IQuestionableCollection<IGiraffe> i2 = i;

    Assert.AreEqual(0, c.Animals.Count());
    Assert.AreEqual(0, i.Animals.Count());
    c.SomeAction();
    i.SomeAction();
    Assert.AreEqual(2, c.Animals.Count());
    Assert.AreEqual(2, i.Animals.Count());
}

Note that you can avoid the cast in QuestionableCollection<T> if you add a where T : class constraint.

default.kramer
  • 5,943
  • 2
  • 32
  • 50
  • Appreciate it kramer, but that's really just side-stepping the issue. It gives us the enumerator but doesn't help with the design issue. – Gene May 12 '15 at 18:13
  • I'll award you the bounty since it's the closest to a solution I think so far. I'll leave it open for now though since it's really just dodging the core issue, but I suppose in the face of language limitations, that's sometime the only option... – Gene May 14 '15 at 11:55
3

Changing IQuestionableCollection to a non-generic IEnumerable sorts the compiler issues.

public interface IQuestionableCollection : IEnumerable {...}

I've seen MS use this pattern in their collections, with the non-generic versions using IEnumerable, and the generic ones using IEnumerable<T>.

Alternatively, making the others IEnumerable<IAnimal> also stops the compiler errors, though it means you get IAnimals back instead of T's when enumerating.

Will
  • 2,512
  • 14
  • 19
  • Appreciate it Will. That's because IEnumerable isn't generic, so there's not conflict in the types unifying. – Gene Apr 28 '15 at 13:10
  • 4
    MS is not using this "pattern" for anything but downward compatibility. The non-generic interfaces are actually much older but removing them would break too muchg existing code. – Georg May 08 '15 at 07:07
0

You could try this:

public interface IAnimal
{

}

public interface IGiraffe : IAnimal
{

}

public interface IQuestionableCollection<T> : IEnumerable<T> where T : IAnimal
{
    void SomeAction();
}

public interface IQuestionableCollection : IQuestionableCollection<IAnimal>
{

}

public class QuestionableCollection<T> : IQuestionableCollection<T>, IEnumerable<T>
    where T : IAnimal
{
    public void SomeAction() { }

    public IEnumerator<T> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}
Pittmyster
  • 62
  • 4
  • I'd agree that works, but what I was really after is how I'd make the base collection non-generic. It seems like I should be able to express that it enumerates a concrete type in the base class. (In this case, there's really no need for the derived interface at all.) – Gene May 12 '15 at 18:15
0

Given the constraints of the language you won't be able to work around the problem of having IQuestionableCollection and IQuestionableCollection both implementing a Generic Interface.

In essence what you are specifying is that IQuestionableCollection implements two possible lists, List and another List. There is no hierarchical relationship here so it impossible to resolve.

That being said you would have to replace IEnumerable with IEnumerable on IQuestionableCollection to provide an inheritance chain to the compiler. In reality there isn't really even a point to declaring the base IQuestionableCollection unless you plan on implementing a vanilla QuestionableCollection

In my updated code I took it out to a consumer of QuestionableCollection to illustrate the enumeration of QuestionableCollection() retains the typing of IGiraffe.

    public interface IAnimal {}

    public interface IGiraffe : IAnimal { }

    public interface IQuestionableCollection : IEnumerable
    {
        void SomeAction();
    }

    public interface IQuestionableCollection<out T> : IQuestionableCollection, IEnumerable<T>
     where T : IAnimal
    { }

    public class QuestionableCollection<T> : IQuestionableCollection<T>
        where T : IAnimal
    {

        private List<T> list = new List<T>(); 

        public IEnumerator<T> GetEnumerator()
        {
            return list.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void SomeAction()
        {
            throw new NotImplementedException();
        }
    }

      class Program
    {
        static void Main(string[] args)
        {
            var questionable = new QuestionableCollection<IGiraffe>();
            foreach (IGiraffe giraffe in questionable)
            {

            }
        }
    }

For the Non Generic Implementation

1 You can always safely upcast

var questionable = new QuestionableCollection();
IEnumerabl<IAnimal> animals = questionable.OfType<IAnimal>();

-or-

2 You can have the NonGeneric QuestionableCollection class implement IEnumerable

 public class QuestionableCollection : IQuestionableCollection, IEnumerable<IAnimal> 
{
    public IEnumerator<IAnimal> GetEnumerator()
    {
        var l = new List<Giraffe>();
        l.Add(new Giraffe());
        l.Add(new Giraffe());
        return l.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void SomeAction()
    {
        throw new NotImplementedException();
    }
}

Which you then can enumerate without a cast operation.

    var questionable = new QuestionableCollection();
    foreach (IAnimal giraffe in questionable)
    {
        var i = giraffe;
    }
muglio
  • 2,048
  • 17
  • 18
  • Appreciate it muglio. Same issue though - what I was really after was having a non-generic base class express that it is an IEnumerable. If the base class is generic, then agreed we only need one interface and there's really no issue. (Even the abstract class here is unnecessary, though I appreciate what you were after given the lack of further definition.) I'll update the question to make that clearer. – Gene May 12 '15 at 18:18
  • @Gene - think I understand the spirit of the question a little bit better. Am I closer here? – muglio May 12 '15 at 21:47
  • You are, but in this case IEnumerable (the non-generic version) is just sidestepping the problem as well, similar to Will's suggestion, in that there's no unification issue since it's target is object. So now you can only enumerate over object, not IAnimal, on the base collection. – Gene May 12 '15 at 21:51
  • @Gene - not entirely true. You can upcast or you can simply have the non generic QuestionableCollection implement IQuestionableCollection, IEnumerable which is essentially what you are wanting to do. See my answer revision. – muglio May 12 '15 at 22:57
  • Sure. Though if you're casting you're losing the benefit of having IEnumerable in the first place right? The goal of having it on the based interface is so that you could treat it/pass it as a non-generic version where relevant (which is convenient particularly with reflection/etc where inspecting the generic type is a bit more of a hassle). Granted convenience isn't enough to justify it, but it certainly seems to me like something I should be able to express (that the base is IEnumerable) and that C# doesn't support it for some reason or there's a better pattern. – Gene May 13 '15 at 20:07
  • I don't like the upcast option but I'm just providing possibilities. Please see my second option. It is alternative to your second implementation. I think mine self documents a little better but between the two it is just a matter of style. You can't express IEnumerable because the the declaration of your IQuestionable is a IQuestionable that is an IEnumerable, and that IQuestionable is also an Enumerable. It is equivalent to you declaring that IQuestionable is an IEnumerable and IEnumerable. – muglio May 13 '15 at 20:46