«Сильно типизированные» универсальные коллекции, которые содержат любые <T>данного интерфейса / класса - PullRequest
0 голосов
/ 19 марта 2019

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

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

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

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

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

Ответы [ 2 ]

3 голосов
/ 20 марта 2019

Можно ли объявить универсальную коллекцию для хранения только объектов, реализующих универсальный интерфейс, инстанцированный с 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>> или что-либо еще.

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

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

0 голосов
/ 19 марта 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.

...