对于这样的场景:
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...
}
Run Code Online (Sandbox Code Playgroud)
编译器将生成错误:
'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
Run Code Online (Sandbox Code Playgroud)
这是有道理的,C#无法解决的两个接口之间确实存在歧义,除非它使用类型约束,而不是像@ericlippert 在这里解释的语言规范那样.
我的问题是我应该如何在这里实现同样的效果呢?
似乎我应该能够表达该集合对于基本接口是可枚举的.(我想提供一组可以在不知道具体类型的情况下使用的方法,以及它使一些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(); }
}
Run Code Online (Sandbox Code Playgroud)
请注意,我必须将我想在两个接口上使用的任何方法移动到基本方法,并为类本身提供两个级别的实现 - 这似乎我在这里跳过了足够的箍我已经有了失踪的东西......
该如何实施?
最简单的解决方法是将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());
}
Run Code Online (Sandbox Code Playgroud)
请注意,QuestionableCollection<T>如果添加where T : class约束,则可以避免强制转换.
将 IQuestionableCollection 更改为非泛型 IEnumerable 可解决编译器问题。
public interface IQuestionableCollection : IEnumerable {...}
Run Code Online (Sandbox Code Playgroud)
我已经看到 MS 在他们的集合中使用了这种模式,非通用版本使用IEnumerable,通用版本使用IEnumerable<T>。
或者,创建其他的IEnumerable<IAnimal>也可以阻止编译器错误,尽管这意味着在枚举时您会得到 IAnimals 而不是 T。