Ответ 1
Нет, если вы не объявите другой интерфейс:
IAB : IA, IB {}
и оба класса реализуют его.
Вы также можете реализовать свой собственный класс коллекции, что-то вроде List<IFirst, ISecond>
, что позволит это.
Рассмотрим следующую иерархию классов:
public interface X { void Foo(); }
public interface Y { void Bar(); }
public class A : X, Y
{
public void Foo() {}
public void Bar() {}
}
public class B : X, Y
{
public void Foo() {}
public void Bar() {}
}
Можно ли каким-либо образом определить список (или любой общий тип, если это важно), который может содержать как A
, так и B
, позволяя мне обрабатывать содержимое указанного списка как X
и Y
? То есть что-то, что позволит мне написать что-то по этому поводу:
var list = ???
list.Add(new A());
list.Add(new B());
list.First().Foo();
list.Last().Bar();
Edit
Чтобы уточнить, типы, с которыми я сейчас сталкиваюсь, это ObservableCollection<T>
(A
) и ReadOnlyObservableCollection<T>
(B
), где интересующие интерфейсы IList<T>
(X
) и INotifyCollectionChanged
(Y
). Очевидно, что я не могу изменить иерархию классов для удовлетворения моих потребностей, поэтому мне нужно другое обходное решение.
Нет, если вы не объявите другой интерфейс:
IAB : IA, IB {}
и оба класса реализуют его.
Вы также можете реализовать свой собственный класс коллекции, что-то вроде List<IFirst, ISecond>
, что позволит это.
Вероятно, вы не хотите изменять класс A и B Таким образом, обертка выполнит работу
public class XYWrapper : X, Y
{
private dynamic InternalObject { get; set; }
public void Foo() { InternalObject.Foo(); }
public void Bar() { InternalObject.Bar(); }
public static implicit operator XYWrapper(A value)
{
var instance = new XYWrapper();
instance.InternalObject = value;
return instance;
}
public static implicit operator XYWrapper(B value)
{
var instance = new XYWrapper();
instance.InternalObject = value;
return instance;
}
}
Итак, вы используете его таким образом:
var list = new List<XYWrapper>();
list.Add(new A());
list.Add(new B());
list.First().Foo();
list.Last().Bar();
Да, если вы полностью реализуете их :
public interface IFoo
{
}
public interface IBar
{
}
public class DualList : IList<IFoo>, IList<IBar>
{
List<IFoo> list1 = new List<IFoo>();
List<IBar> list2 = new List<IBar>();
#region IList<IFoo> Members
int IList<IFoo>.IndexOf(IFoo item)
{
return list1.IndexOf(item);
}
// Etc etc
#endregion
#region IList<IBar> Members
int IList<IBar>.IndexOf(IBar item)
{
return list2.IndexOf(item);
}
// Etc etc
#endregion
}
Этот контейнер действует как два независимых списка разных типов. Чтобы использовать его, вы можете сделать его так:
((IList<IFoo>)dualList).Add(item);
Я сомневаюсь в мудрости этого. У вас определенно будет проблема с сериализацией такого списка с любым известным сериализатором.
Вы можете объявить,
public class SomeType<T> where T: X, Y
{
public void Do(T t)
{
t.Foo();
t.Bar();
}
}
как здесь, вы узнаете, что T
должен реализовывать как X
, так и Y
из-за ограничений типа общего типа.
Однако, когда вы приходите для создания экземпляра SomeType
, вам понадобится один тип, который наследует или реализует как A
, так и B
.
Если вы просто добавите,
interface Z : X, Y { }
затем измените A
и B
на реализацию Z
public class A : Z
{
public void Foo() {}
public void Bar() {}
}
public class B : Z
{
public void Foo() {}
public void Bar() {}
}
вы можете просто использовать Z
как общий тип.
Если вы создаете составной интерфейс двух интерфейсов:
interface IA
{
void Foo();
}
interface IB
{
void Bar();
}
interface IAB:IA,IB{}
Теперь возьмите два класса:
class A:IA,IB
{
public void Foo(){}
public void Bar(){}
}
class B:IA,IB
{
public void Foo(){}
public void Bar(){}
}
и создавать подклассы, реализующие составной интерфейс (никакой реальной реализации не требуется, поскольку она уже присутствует для обоих интерфейсов в составном интерфейсе):
class AX:A,IAB
{
}
class BX:B,IAB
{
}
Теперь вы можете...
void Main()
{
List<IAB> list=new List<IAB>();
list.Add(new AX());
list.Add(new BX());
}
Если у вас будут разные конкретные типы в списке, каждый из которых реализует другой интерфейс (потенциально инклюзивный или взаимоисключающий), тогда вам нужно протестировать для каждого интерфейса, который часто выполняется с помощью instance as ISomeInterface
и проверки, если результат не имеет значения.
Обратите внимание, что ниже будут работать типы, которые реализуют как IFooable, так и IBarrable, или просто реализуют одно из двух. Таким образом, он дает вам много гибкости при сохранении безопасного типа. Единственным недостатком является то, что конкретный тип позволяет реализовать интерфейс маркера напрямую и вообще не реализовывать IFooable или IBarrable.
// marker interface
public interface IFooBar
{
}
public interface IFooable:IFooBar
{ void Foo(); }
public interface IBarrable:IFooBar
{ void Bar(); }
public class FooBarProcessor
{
public void ProcessFooBars(IList<IFooBar> foobars)
{
foreach(var foobar in foobars)
{
// feature detection to see which interfaces the instance implements
IBarrable bar = foobar as IBarrable;
if(bar != null)
bar.Bar();
IFooable foo = foobar as IFooable;
if(foo != null)
foo.Foo();
}
}
}
Если у вас нет контроля над вашими интерфейсами, вы можете использовать List<object>
, который несколько менее безопасен по типу, но все же нам метод foobar as IBarrable
.
Если вы все еще хотите использовать интерфейс маркера, чтобы ограничить IList немного больше, вы можете обернуть интерфейс, который вы не можете изменить, с помощью интерфейса вашего создания так, где IOutOfMyControlFooable - это любой интерфейс, который вы не можете изменить:
public interface IFooable : IFooBar, IOutOfMyControlFooable
{ void Foo(); }
public interface IBarrable : IFooBar, IOutOfMyControlBarrable
{ void Bar(); }
public class FooBarProcessor
{
public void ProcessFooBars(IList<IFooBar> foobars)
{
foreach(var foobar in foobars)
{
// feature detection to see which interfaces the instance implements
IBarrable bar = foobar as IBarrable;
if(bar != null)
bar.Bar();
IFooable foo = foobar as IFooable;
if(foo != null)
foo.Foo();
}
}
}
Используйте такой тип обертки, как это:
public class Wrapper<T>() {
public WrapperObsCollection(ObservableCollection<T> collection) {
IsReadOnly = false;
ObsCollection = collection;
ROObsCollection = null;
}
public WrapperObsCollection(ReadOnlyObservableCollection<T> collection : base {
IsReadOnly = true;
ObsCollection = null;
ROObsCollection = collection;
}
public bool IsReadOnly { get; private set; }
public ObservableCollection<T> ObsCollection { get; private set; }
public ReadOnlyObsrevableCollection<T> ROObsCollection { get; private set; }
}
- и т.д.
Вы пробовали дополнительный класс, подобный этому...
public class ABList<T> : List<T> where T : X, Y
{
// implement me
}
... и использовать его так:
var list = new ABList<dynamic>(); // issue here; see comment below
list.Add(new A());
list.Add(new B());
list.First().Foo();
list.Last().Bar();
ОБНОВЛЕНИЕ. Решение не работает из-за проблемы с компилятором (спасибо @mikez). Если проверка типа не имеет значения, решение можно оптимизировать, чтобы просто использовать динамический список следующим образом:
var list = new List<dynamic>();
list.Add(new A());
list.Add(new B());
list.First().Foo();
list.Last().Bar();