Одновременное выделение объекта на два интерфейса для вызова общего метода
Я хочу вызвать общий метод, который ограничивает тип ввода 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);
}
}
?
Отражение, вероятно, позволяет делать вызов, но я хотел бы оставаться внутри языка.
Ответы
Ответ 1
Является ли динамическое ключевое слово С# 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
Похоже, вы не понимаете, как работают дженерики: при вызове метода, который имеет общий параметр T
, T
должен быть статически известен во время компиляции. Хотя компилятор иногда может вывести его (и поэтому вам не всегда нужно явно записывать его), при вызове метода должен предоставляться некоторый T
. В вашем случае все, что вам известно, это то, что obj
- это IA
и IB
, но это не дает вам достаточно информации для вызова foo<T>
, так как вы понятия не имеете, что T
должно быть, Вам придется либо использовать отражение, и применить его к определенному типу, который реализует как IA
, так и IB
, или сделать более резкое изменение дизайна.
Ответ 3
Я согласен с другими респондентами в том, что у вас, вероятно, проблема с дизайном, если вам нужно это сделать, но вы можете выполнить его с прокси-объектом, который реализует оба интерфейса и делегирует вызовы двум экземплярам литого интерфейса неизвестного объекта, Теперь, когда вы вызываете этот метод, вы можете создать прокси для любого типа, который поддерживает оба интерфейса.
Ответ 4
Ваш метод 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));
}
}
Ответ 5
вам нужно будет определить третий тип (возможно, интерфейс), который наследуется от обоих интерфейсов. если у вас есть такие ограничения, то определенно вы должны иметь их. иначе это непригодно. если этот (obj is IB && obj is IB)
, то obj - именно этот тип.
Ответ 6
Я не считаю, что вы хотите, если нет типа или интерфейса, который применим ко всем объектам, которые вы, возможно, захотите принять. Если у вас есть контроль над типами вещей, которые вы будете обрабатывать, вы должны определить интерфейс, который "наследует" все ваши ограничения, а затем функция принимает параметр этого типа. Если вы также хотите указать базовый тип в своем ограничении, определите интерфейс:
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 (из клиента).
Обратите внимание, что это будет работать, только если передаваемый класс явно реализует либо IDIsposableEnumerableOfStringAndSelf (Of Customer), либо IDIsposableEnumerableOfStringAndSelf (Of T) для некоторого T, который является подтипом Customer. Интерфейсы не печатаются в утином (важно, поскольку это возможно и иногда полезно иметь интерфейс без элементов, например, чтобы указать, что класс promises является неизменным).
Ответ 7
Я не потворствую этой практике, но вот два варианта, о которых другие не говорили:
Если у вас есть контроль над 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
}
Абонент:
foo(MultiType<IA, IB>.Create(obj))
Обратите внимание, что это может быть "закодировано": экземпляр MultiType<MultiType<IDictionary<string, string>, IList<int>>, MultiType<INotifyPropertyChanged, INotifyCollectionChanged>
позволит вам разобраться с вещью в виде словаря, списка целых чисел, простого перечисления, INotifyPropertyChanged и INotifyCollectionChanged, сразу.
Но опять-таки, это действительно плохой запах кода - вероятно, класс, с которым нужно бороться таким образом, слишком много делает.