Класс с несколькими IEnumerable <T> интерфейсами на нем - Что делать с не-общим методом?
Думаю, я понимаю, почему IEnumerable<T>
наследует от IEnumerable
после прочтения сообщения:
Почему IEnumerable<T>
наследуется от IEnumerable?
Однако я не уверен, как лучше всего реализовать не-общий метод при использовании 2-х общих интерфейсов? Вот пример кода, который я пишу:
public interface IComponentA { /* ... Interface A Code ... */ }
public interface IComponentB { /* ... Interface B Code ... */ }
public class ComponentModel: IEnumerable<IComponentA>, IEnumerable<IComponentB>
{
public ComponentModel() { }
private List<IComponentA> ListOfComponentA = new List<IComponentA>();
private List<IComponentB> ListOfComponentB = new List<IComponentB>();
// ... Some public methods to add and remove components (for A and B).
IEnumerator<IComponentA> IEnumerable<IComponentA>.GetEnumerator()
{
return ListOfComponentA.GetEnumerator();
}
IEnumerator<IComponentB> IEnumerable<IComponentB>.GetEnumerator()
{
return ListOfComponentB.GetEnumerator();
}
// The fact that IEnumerable<T> inherits from the non-generic IEnumerable
// now means I have to deal with this.
IEnumerator IEnumerable.GetEnumerator()
{
// Throwing a NotImplementedException is probably not a good idea
// so what should I put in here?
throw new NotImplementedException();
}
}
Предложения о том, что добавить в универсальный метод, приветствуются.
Ответы
Ответ 1
Я бы, наверное, этого не сделал. Это может смутить пользователя, чтобы перечисление перечислило разные вещи в зависимости от ссылки на интерфейс, вызывающей его, и, конечно же, вопрос о том, что также возвращает общий доход.
Вместо этого вы можете просто вывести версию списков в режиме "только для чтения" как итератор:
public class ComponentModel
{
public ComponentModel() { }
private List<IComponentA> ListOfComponentA = new List<IComponentA>();
private List<IComponentB> ListOfComponentB = new List<IComponentB>();
public IEnumerable<IComponentA> AComponents
{
get { return ListOfComponentA.Skip(0); }
}
public IEnumerable<IComponentB> BComponents
{
get { return ListOfComponentB.Skip(0); }
}
...
}
Используя Skip(0)
, вы возвращаете итератор, и это предотвращает возврат к List<IComponentA>
и изменение List
из-под вас.
Вы также можете использовать ReadOnlyCollection
, конечно, но это довольно неуклюжие, поскольку они бросают, когда вы пытаетесь сделать мутирующие операторы.
Итак, теперь вы можете выполнить итерацию:
foreach(var a in myModel.AComponents)
{
...
}
foreach(var b in myModel.BComponents)
{
...
}
Кроме того, IF списки компонентов A и B всегда имеют одинаковую длину, вы можете иметь перечислитель по сравнению с Tuple
из них в .NET 4.0 и используя Метод Linq Zip():
public IEnumerable<Tuple<IComponetA, IComponetB>> Components
{
get
{
return ListOfComponentA.Zip(ListOfComponentB, (a,b) => Tuple.Create(a,b));
}
}
Ответ 2
Принятый ответ в точности правильный; вы должны реализовать вещи, которые имеют две последовательности, имеющие две последовательности, а не две последовательности.
Доказанная до сих пор причина, почему это плохая идея, достаточна, а именно, что она обычно менее запутанна, когда вы выбираете композицию над наследованием. Тем не менее, я считаю, что полезно отметить, что существует еще одна причина, по которой внедрение двух разных интерфейсов IEnumerable<T>
- это действительно плохая идея: мы добавили общую ковариацию к IEnumerable<T>
в С# 4.
Общая ковариантность означает, что вы можете передать череду черепах методу, который принимает последовательность животных, потому что черепахи - животные. Итак, что же тогда делает runtime в этом случае?
class Weird : IEnumerable<Turtle>, IEnumerable<Giraffe> { ... }
...
Weird weird = whatever;
foreach(Animal animal in weird)
{
...
Является ли animal
черепахой или жирафом? Время выполнения должно выбрать один из них, и совершенно непонятно, какой из них выбрать. Это одна из редких ситуаций, когда спецификация С# не говорит, что происходит; это поведение, определяемое реализацией. Избегайте, избегайте, избегайте.
Ответ 3
Кошка - это не последовательность ног, глаз и бакенбардов. Скорее, у него есть последовательность ног, глаз и бакенбардов. Состав над наследованием.
Вместо того, чтобы ComponentModel
считаться двумя полностью несвязанными последовательностями IComponentA
и IComponentB
, вы должны удалить это и просто добавить методы:
public IEnumerable<IComponentA> GetSequenceOfComponentA() { // }
public IEnumerable<IComponentB> GetSequenceOfComponentB() { // }
(Что вы ожидаете от foreach(var x in componentModel) { // }
? Что происходит здесь: public void M(IEnumerable<IComponentA> a) { }
и public void M(IEnumerable<IComponentB> b) { }
, а затем M(componentModel)
?)
Ответ 4
возможно, что-то в этом направлении будет работать.
он выполняет публичный класс MyCollection (T): IEnumerable (T)
использовать один и тот же тип для обработки двух разных типов данных
вы можете пойти дальше с MyCollection (T, X, Y, Z): IEnumerable (T)
где X, Y, Z - списки ваших других типов
за исключением того, что T будет вашим главным итератором, выполняющим внутреннюю итерацию ваших других списков параллельно.
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Start.....");
for ( int i = 0; i < 10; i++)
{
IEnumerable collection;
Console.WriteLine();
if (i % 2 == 0)
{ Console.Write("Ints..... ");
collection = new MyCollection<int>(new int[] { 1,2,3 }); }
else
{ Console.Write("Strings..... ");
collection = new MyCollection<string>(new string[] { "x","y","z" } ); }
foreach (var item in collection) { Console.Write(item); }
Console.WriteLine();
}
Console.WriteLine(); Console.WriteLine("End....."); Console.ReadLine();
}
}
public class MyCollection<T> : IEnumerable<T>
{
private T[] _Tdata;
public MyCollection4(T[] arrayOfT)
{
_Tdata = arrayOfT;
}
public IEnumerator<T> GetEnumerator()
{
var listofT = new List<T>(_Tdata);
return listofT.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}