Приведение объекта к двум интерфейсам одновременно для вызова универсального метода - PullRequest
12 голосов
/ 24 мая 2010

Я хочу вызвать универсальный метод, который ограничивает тип ввода T для реализации двух интерфейсов:

interface IA { }
interface IB { }
void foo<T>(T t) where T : IA, IB { }

Как исправить последнюю строку

void bar(object obj)
{
    if (obj is IA && obj is IB)
    {
        foo((IA && IB)obj);
    }
}

?

Отражение, вероятно, позволяет сделать звонок, но я бы хотел остаться в пределах языка.

Ответы [ 7 ]

6 голосов
/ 24 мая 2010

Похоже, вы неправильно понимаете, как работают обобщенные элементы: при вызове метода, который имеет обобщенный параметр T, T должны быть статически известны во время компиляции.Хотя компилятор иногда может вывести его (и поэтому вам не всегда нужно явно записывать его), некоторые T должны быть указаны при вызове метода.В вашем случае все, что вы знаете, это то, что obj - это IA и IB, но это не дает вам достаточно информации, чтобы позвонить на foo<T>, так как вы не знаете, каким должен быть T,Вам нужно будет либо использовать отражение, приведение к определенному типу , который реализует оба типа IA и IB, либо внести более существенное изменение в дизайн.

4 голосов
/ 24 мая 2010

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

3 голосов
/ 25 мая 2010

Выводит ли динамическое ключевое слово C # 4.0 из тюрьмы (в основном) бесплатно? В конце концов - вы уже делаете проверку типа.

interface IC : IA, IB { }

void bar(object obj)
{
  if (obj is IA && obj is IB)
  {
    IC x = (dynamic)obj;
    foo(x);
  }
}

Разбивается ли это, если foo пытается привести параметр к T? Я не знаю.

2 голосов
/ 06 марта 2017

Я не одобряю эту практику, но есть два варианта, которые другие не упомянули:

Если вы контролируете foo

Тогда вы можете изменить его на:

void foo(IA asA, IB asB)
{
    if (!ReferenceEquals(isA, isB)) throw new ArgumentException("isA and isB must be the same object");
    // Your code here
}

Это позволяет вашему телефонному коду стать:

foo(obj as IA, obj as IB);

Это не красиво, но это может быть вариант.

Если вы любите пандемические хаки

Если вам часто приходится этим заниматься, то это неприятный запах и лучший дизайн.

Но если у вас нет выбора, и это "необходимо" повсеместнотогда это может сделать вашу жизнь проще, потому что вам не нужно тратить время на создание классов, которые реализуют ваши интерфейсы IA и IB:

/// <summary>
/// An ugly hack for when you don't want to create a new wrapper class that inherits from and implements two other interfaces
/// </summary>
/// <typeparam name="TOne"></typeparam>
/// <typeparam name="TTwo"></typeparam>
public sealed class MultiType<TOne, TTwo>
{
    /// <summary>
    /// The contained item
    /// </summary>
    private readonly object _containedObject;

    /// <summary>
    /// The contained item as a TOne
    /// </summary>
    public TOne AsOne => (TOne)_containedObject;

    /// <summary>
    /// The contained item as a TTwo
    /// </summary>
    public TTwo AsTwo => (TTwo)_containedObject;

    /// <summary>
    /// Creates a new MultiType that exposes the given item as two different classes
    /// </summary>
    /// <param name="containedObject"></param>
    private MultiType(object containedObject)
    {
        if (containedObject is TOne && containedObject is TTwo)
            _containedObject = containedObject;
        else
            throw new Exception("The given object must be both a TOne and a TTwo");
    }

    /// <summary>
    /// Creates a new MultiType that exposes the given thing as both a TOne and a TTwo
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="thing"></param>
    /// <returns></returns>
    public static MultiType<TOne, TTwo> Create<T>(T thing)
        where T : TOne, TTwo
        => new MultiType<TOne, TTwo>(thing);
}

Использование:

void foo(MultiType<IA, IB> thing)
{
    thing.AsOne... // Your code dealing with the thing as an IA
    thing.AsTwo... // Your code dealing with the thing as an IB
}

Caller:

foo(MultiType<IA, IB>.Create(obj))

Обратите внимание, что это может быть "цепочкой": экземпляр MultiType<MultiType<IDictionary<string, string>, IList<int>>, MultiType<INotifyPropertyChanged, INotifyCollectionChanged> позволит вам иметь дело с вещью в виде словаря, списка целых чисел, простого перечисления, INotifyPropertyChanged и INotifyCollectionChanged,все сразу.

Но опять же, это действительно плохой кодll - вероятно, класс, с которым нужно иметь дело таким образом, делает слишком много.

2 голосов
/ 25 мая 2010

Ваш метод bar также должен быть универсальным с теми же ограничениями, что и foo.

Но если вы действительно хотите решить эту проблему, вы можете создать оболочку для объекта, который реализует оба интерфейсакоторый делегирует все вызовы обернутым экземплярам:

class ABWrapper : IA, IB {
  IA _a;
  IB _b;
  public Wrapper(IA a) {
    if (!(a is IB)) throw new ArgumentException();
    _a = a;
    _b = (IB)a;
  }
  public Wrapper(IB b) {
    if (!(b is IA)) throw new ArgumentException();
    _a = (IA)b;
    _b = b;
  }
  // explicit implementation for IA and IB delegating to _a and _b
}

И используйте его так:

static void bar(object obj) {
  if (obj is IA && obj is IB) {
    foo(new ABWrapper((IA)obj)); 
  }
}
0 голосов
/ 01 декабря 2010

Я не верю, что то, что вы хотите, возможно, если нет типа или интерфейса, который применим ко всем объектам, которые вы, возможно, захотите принять. Если у вас есть контроль над типами вещей, которые вы будете обрабатывать, вы должны определить интерфейс, который «наследует» все ваши ограничения, а затем сделать так, чтобы функция принимала параметр этого типа. Если вы также хотите включить базовый тип в ограничение, определите интерфейс:

Interface ISelf(Of Out T)
  Function Self As T
End Interface
, а затем составной интерфейс ограничения наследует ISelf (Of DesiredBaseType). Например, если вы собираетесь использовать процедуру приема объектов, которая наследуется от типа Customer и реализует как IDisposable, так и IEnumerable (Of String) [глупый произвольный пример], определите:
Interface IDisposableEnumerableOfStringAndSelf(Of T)
  Inherits IDisposable, IEnumerable(Of String), ISelf(Of T)
End Interface

и затем эти подпрограммы принимают IDIsposableEnumerableOfStringAndSelf (Of Customer).

Обратите внимание, что это будет работать только в том случае, если передаваемый класс явно реализует либо IDIsposableEnumerableOfStringAndSelf (Of Customer), либо IDIsposableEnumerableOfStringAndSelf (Of T) для некоторого T, являющегося подтипом Customer. Интерфейсы не являются типизированными по типу утки (факт, который важен, так как возможно и иногда полезно иметь интерфейс без членов, например, чтобы указать, что класс обещает быть неизменным).

0 голосов
/ 24 мая 2010

вам нужно будет определить третий тип (возможно, интерфейс), который наследуется от обоих интерфейсов. если у вас есть такие ограничения, то определенно у вас должно быть. в противном случае это непригодно для использования. если это (obj is IB && obj is IB), тогда obj именно этого типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...