Подтвердить что ты не робот

Можно ли определить список любого типа, который реализует несколько интерфейсов?

Рассмотрим следующую иерархию классов:

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). Очевидно, что я не могу изменить иерархию классов для удовлетворения моих потребностей, поэтому мне нужно другое обходное решение.

4b9b3361

Ответ 1

Нет, если вы не объявите другой интерфейс:

IAB : IA, IB {}

и оба класса реализуют его.

Вы также можете реализовать свой собственный класс коллекции, что-то вроде List<IFirst, ISecond>, что позволит это.

Ответ 2

Вероятно, вы не хотите изменять класс 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();

Ответ 3

Да, если вы полностью реализуете их :

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

Я сомневаюсь в мудрости этого. У вас определенно будет проблема с сериализацией такого списка с любым известным сериализатором.

Ответ 4

Вы можете объявить,

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 как общий тип.

Ответ 5

Если вы создаете составной интерфейс двух интерфейсов:

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

Ответ 6

Если у вас будут разные конкретные типы в списке, каждый из которых реализует другой интерфейс (потенциально инклюзивный или взаимоисключающий), тогда вам нужно протестировать для каждого интерфейса, который часто выполняется с помощью 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();
        }
    }
}

Ответ 7

Используйте такой тип обертки, как это:

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

- и т.д.

Ответ 8

Вы пробовали дополнительный класс, подобный этому...

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