继承的通用类型统一

时间:2015-04-27 23:32:59

标签: c# generics

对于这样的场景:

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

编译器将生成错误:

'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

这是有道理的,除非它使用类型约束,否则C#无法解决这两个接口之间的模糊性,它不符合语言规范,因为@ericlippert解释{{3 }}

我的问题是我应该如何在这里实现同样的效果?

似乎我应该能够表达该集合对于基本接口是可枚举的。 (我想提供一组可以在不知道具体类型的情况下使用的方法,以及它使一些API /反射代码更清晰,所以我想将基本集合保持为非如果可能的话通用。否则,不需要两个接口。)

我能想到的唯一可编程实现类似于:

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

请注意,我必须将我想在两个接口上使用的任何方法移动到基本方法,并为类本身提供两个级别的实现 - 这似乎是我跳跃通过这里足够的箍,我得错过了一些东西......

应如何实施?

4 个答案:

答案 0 :(得分:5)

最简单的解决方法是将IEnumerables从“is-a”更改为“has-a”,如下所示:

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

请注意,如果添加QuestionableCollection<T>约束,则可以避免在where T : class中投射。

答案 1 :(得分:3)

将IQuestionableCollection更改为非通用IEnumerable会对编译器问题进行排序。

class DGPostsTableView: DGTableView, DGTableViewAble {
    typealias DGTableViewItemType = Post

    ...
}

我已经看到MS在其馆藏中使用此模式,非通用版本使用public interface IQuestionableCollection : IEnumerable {...} ,而通用版本使用IEnumerable

或者,使其他IEnumerable<T>也会停止编译器错误,但这意味着在枚举时会返回IAnimals而不是T&#39; s。

答案 2 :(得分:0)

你可以试试这个:

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

答案 3 :(得分:0)

鉴于语言的限制,您将无法解决IQuestionableCollection和IQuestionableCollection都实现通用接口的问题。

实质上,您指定的是IQuestionableCollection实现两个可能的列表,List和另一个List。这里没有等级关系,因此无法解决。

话虽如此,您必须在IQuestionableCollection上用IEnumerable替换IEnumerable,以便为编译器提供继承链。实际上除非你计划实现一个vanilla QuestionableCollection,否则甚至没有必要声明基础IQuestionableCollection

在我更新的代码中,我把它带到了QuestionableCollection的消费者,以说明QuestionableCollection()的枚举保留了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)
            {

            }
        }
    }

非通用实施

1您可以随时安全地向上播放

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

<强> - 或 -

2您可以让NonGeneric QuestionableCollection类实现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();
    }
}

然后,您可以在没有强制转换操作的情况下枚举。

    var questionable = new QuestionableCollection();
    foreach (IAnimal giraffe in questionable)
    {
        var i = giraffe;
    }