«Строго типизированные» общие коллекции, которые содержат любые ‹T› данного интерфейса/класса

Можно ли объявить универсальную коллекцию для хранения только объектов, реализующих универсальный интерфейс с любым <T>?

Мой вопрос сводится к следующему: если я хочу/должен хранить объекты, реализующие универсальный интерфейс, есть ли лучший способ выразить этот факт, чем использовать неуниверсальную коллекцию или (общий из <Object>).

Пример:

// An example Generic Interface
interface ISyncInterface<T>
{
    Task DoSync();
    IEnumerable<T> NewItems { get; }
}

// a manager-class that registers different classes implementing
// the generic interface.
// The code works - can it be done better?
class Manager
{
    private List<Object> _services = new List<Object>(); // <- works but is basically non generic
    // however the RegisterService() ensures that only correct types can be added.

    // would like to have something like below to indicate the Interface-Type
    // however: this would only allow _services2.Add to hold types of ISyncInterface<Object>
    //    - ISyncInterface<ServiceA_DTO> would fail.
    private List<ISyncInterface<Object>> _services2 = new List<ISyncInterface<Object>>();

    void RegisterService<T, U>(T service)
        where T : ISyncInterface<U>
    {
        _services.Add(service); // <- works e.g. for SyncServiceA 

        // _services2.Add(service); // <- FAILS for SyncServiceA - no conversion
        // _services2.Add((ISyncInterface<Object>) service); // <- FAILS also - no explicit cast
    }
}

// SETUP - The classes used above. Just to clarify.
class ServiceA_DTO { }
class ServiceB_DTO { }

class SyncServiceA : ISyncInterface<ServiceA_DTO>
{
    public Task DoSync() {}
    public IEnumerable<ServiceA_DTO> NewItems { get; }
}

class SyncServiceB : ISyncInterface<ServiceB_DTO>
{
    public Task DoSync() {}
    public IEnumerable<ServiceB_DTO> NewItems { get; } 
}

Это вообще возможно? Любой совет высоко ценится!

Обновление: новый, более подробный код для прояснения проблемы.

Ниже было предложение создать общий интерфейс на основе не общего. Но, как следствие, все реализующие классы универсального интерфейса должны будут реализовать неуниверсальные методы, свойства и т. д. - или есть способ обойти это?

Спасибо за ваш вклад!


person papa_bear    schedule 19.03.2019    source источник
comment
Какие участники есть у MyInterface<T>? Возможно, вы сможете сделать его ковариантным в T, тогда вы сможете создать List<MyInterface<object>>.   -  person Lee    schedule 19.03.2019
comment
если бы частные объекты List были частными функциями, возвращающими списки из ‹T›, U мог бы установить их = new List‹T›() для возврата к процессу, использующему их. Если U пытается сравнить список с проходящими через него интерфейсами, U должен будет создать этот список в вашем конструкторе для использования или создать его в функции сравнения. Если вы используете статический список во всех интерфейсах, вам потребуется ключевое слово static для элемента, а также для процесса добавления.   -  person Joseph Poirier    schedule 19.03.2019
comment
Важно помнить, что каждый MyInterface<T>, где T отличается, является другим классом — думайте о них как о MyInterfaceInt и MyInterfaceString. Как бы вы создали класс, который может справиться с этим? Экземпляры универсальных классов совместно используют реализацию, а не иерархию типов.   -  person NetMage    schedule 19.03.2019


Ответы (2)


Можно ли объявить универсальную коллекцию для хранения только объектов, реализующих универсальный интерфейс, созданный с помощью any T?

Краткий ответ: нет.

Более длинный ответ: нет, потому что это бесполезно.

Давайте рассмотрим простой универсальный интерфейс:

interface I<T> { T Get(); }

И куча объектов, которые это реализуют:

class Lion : I<Lion> 
{
  public Lion Get() => this;
}
class TaxPolicyFactory : I<TaxPolicy>
{
  public TaxPolicy Get() => new TaxPolicy();
}
class Door: I<Doorknob>
{
  public Doorknob Get() => this.doorknob;
  ...
}

Хорошо, теперь предположим, что у вас есть List<I<ANYTHING>>, как вы хотите:

var list = new List<I<???>> { new TaxPolicyFactory(), new Lion(), new Door() };

У вас есть список с фабрикой налоговой политики, львом и дверью. Эти типы не имеют ничего общего друг с другом; нет операции, которую вы можете выполнить с каждым из этих объектов. Даже если бы вы могли вызвать Get для каждого из них, тогда у вас была бы последовательность с налоговой политикой, львом и дверной ручкой, и что вы собираетесь с этим делать?

Ничего, вот что. Ограничение «реализует интерфейс I<T> для любого T» просто бесполезно в C#, поэтому его невозможно выразить.

Похоже, у вас проблема "XY". Это проблема, когда вы имеете в виду плохое решение, а теперь задаете вопросы о своем плохом решении. Задайте нам вопрос о реальной проблеме, которая у вас есть, а не о плохой идее, которая у вас есть для ее решения. В чем настоящая проблема?


ОБНОВЛЕНИЕ: с новой информацией в вопросе теперь стало намного понятнее. Функция, которую вы хотите, называется универсальная ковариация интерфейса, и это была моя любимая функция в C# 4.

Если вы обновите определение интерфейса до

interface ISyncInterface<out T> { ... }

тогда вы можете использовать ISyncInterface<String> в контексте, где ожидается ISyncInterface<Object>. Например, вы можете поместить ISyncInterface<Giraffe> в List<ISyncInterface<Animal>> или что-то в этом роде.

Однако вы должны убедиться, что ваше определение интерфейса использует только T в ковариантно допустимой позиции. Ваш интерфейс действителен, как указано, но если, например, вы когда-нибудь захотите добавить метод void M(T t); в свой интерфейс, он больше не будет ковариантно действительным. «out» — это мнемоника, говорящая вам, что T может использоваться только как выход методов. Поскольку IEnumerable<T> также ковариантно допустимо, все в порядке; нет входов T в IEnumerable<T>.

Кроме того, вариантность работает только с универсальными интерфейсами и делегатами, и различные типы должны быть ссылочными типами. Вы не можете поместить ISyncInterface<int> в List<ISyncInterface<Object>>, потому что int не является ссылочным типом.

На SO много сообщений о ковариантности и контравариантности; вы также должны прочитать документацию Microsoft. Это может сбивать с толку. Если вас интересуют исторические подробности того, как мы разработали и внедрили эту функцию, см. мой блог.

person Eric Lippert    schedule 19.03.2019
comment
Спасибо за ваш вклад. Очевидно, мои намерения не были ясны из приведенного изначально примера. Я обновил код и предоставил гораздо более подробный пример. Мой вопрос сводится к следующему: если я хочу/должен хранить объекты, реализующие универсальный интерфейс, есть ли лучший способ выразить это, кроме как с использованием неуниверсальной коллекции или (универсального ‹Object›), который лучше выражает этот факт. Не нашел решения, поэтому спросил здесь... - person papa_bear; 20.03.2019
comment
@papa_bear: Вы спрашиваете, как добиться общей ковариантности интерфейса? Это возможно в С#; просто добавьте out T к вашему определению интерфейса. Затем вы можете поместить ISync<string> в List<ISync<Object>> или ISync<Giraffe> в List<ISync<Animal>> или что-то еще. Тем не менее, вы должны соблюдать правила ковариантно допустимых интерфейсов, если вы это сделаете. - person Eric Lippert; 20.03.2019
comment
@papa_bear: я добавлю обновление к ответу на этот счет. - person Eric Lippert; 20.03.2019
comment
Спасибо за ваш вклад. Я прочитал несколько статей (со)вариант, и это то, что я искал, поэтому я отмечу ваш ответ как ответ. Последний вопрос: насколько я понял, применяется следующее правило: как только у меня есть интерфейс хотя бы с ОДНИМ ин- или контравариантным типом (например, Interface Test<out TOk, TInvariant>{ .... }, ЕДИНСТВЕННЫЙ способ хранить объекты разных типов в коллекции — это либо использовать не дженерики или общая форма: var objectsOfInterface = new List<Object>(); Правильно ли это? - person papa_bear; 20.03.2019
comment
@papa_bear: см. dotnetfiddle.net/BLhIPF для простой демонстрации. Вы можете хранить больше производных экземпляров и неявно приводить их к базовым типам. Вы можете либо использовать коллекцию напрямую (дает вам все), либо использовать интерфейс. Эрик Липперт объяснил, почему не имеет логического смысла иметь оба на одном и том же интерфейсе по адресу blogs.msdn.microsoft.com/ericlippert/2009/12/03/ . - person Brian; 20.03.2019

Возможно, вы можете попробовать что-то вроде этого:

  public interface MyInterface
  {//methods common to all types
    void FirstMethod();
  }
  public interface MyInterface<T> : MyInterface
  {//methods specific to a type
    void FirstMethod(T parameter);
  }
  public class MyClassThatHandlesAllInterfaces
  {
    private List<MyInterface> _allInterfacesT; //first interface in the chain
    public void AddInterface<T>(MyInterface<T> ifToAdd)
    {
      _allInterfacesT.Add(ifToAdd); // <- this is what I'd like to do
    }
  }

Я использую эту схему довольно часто. Поскольку я не знаю всех подробностей о вашем сценарии, он может вам не подойти.

Но это может помочь другим людям, которые ищут в Google.

person Jonathan Alfaro    schedule 19.03.2019