对于这样的场景:
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(); }
}
请注意,我必须将我想在两个接口上使用的任何方法移动到基本方法,并为类本身提供两个级别的实现 - 这似乎是我跳跃通过这里足够的箍,我得错过了一些东西......
应如何实施?
答案 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;
}