Reputation: 2956
I want to call a generic method that constrains the input type T to implement two interfaces:
interface IA { }
interface IB { }
void foo<T>(T t) where T : IA, IB { }
How can I fix the last line of
void bar(object obj)
{
if (obj is IA && obj is IB)
{
foo((IA && IB)obj);
}
}
?
Reflection probably allows to do the call, but I would like to stay within the language.
Upvotes: 13
Views: 2985
Reputation: 5774
I'm not condoning this practice, but here are two options that others didn't mention:
foo
Then you can refactor it to:
void foo(IA asA, IB asB)
{
if (!ReferenceEquals(isA, isB)) throw new ArgumentException("isA and isB must be the same object");
// Your code here
}
This allows your calling code to become:
foo(obj as IA, obj as IB);
It isn't pretty, but it might be an option.
If you find yourself needing to do this a lot, then that's a bad smell and there's a better design.
But if you have no choice and this is "needed" all over the place, then this might make your life easier because you don't have to waste time creating classes that implement your IA
and IB
interfaces:
/// <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);
}
Usage:
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))
Note that this can be "chained": an instance of MultiType<MultiType<IDictionary<string, string>, IList<int>>, MultiType<INotifyPropertyChanged, INotifyCollectionChanged>
would let you deal with a thing as a dictionary, list of integers, plain enumerable, INotifyPropertyChanged, and INotifyCollectionChanged, all at once.
But again, it's a really bad code smell--likely the class that needs to be dealt with in that way is doing way too much.
Upvotes: 2
Reputation: 81247
I don't believe what you want is possible if there is no type or interface which is applicable to all objects that you might want to accept. If you have control over the types of things that you'll be handling, you should define an interface which "inherits" all your constraints, and then have the function accept a parameter of that type. If you also want to include a base type in your constraint, define an interface:
Interface ISelf(Of Out T) Function Self As T End Interfaceand then have the composite constraint interface inherit ISelf(Of DesiredBaseType). For example, if you are going to have a routine accept objects which derive from type Customer and implement both IDisposable and IEnumerable(Of String) [silly arbitrary example], define:
Interface IDisposableEnumerableOfStringAndSelf(Of T) Inherits IDisposable, IEnumerable(Of String), ISelf(Of T) End Interface
and then have those routines accept an IDIsposableEnumerableOfStringAndSelf(Of Customer).
Note that this will only work if the class being passed in explicitly implements either IDIsposableEnumerableOfStringAndSelf(Of Customer) or else IDIsposableEnumerableOfStringAndSelf(Of T) for some T that's a subtype of Customer. Interfaces are not duck-typed (a fact which is important, since it's possible and sometimes useful to have an interface with no members, e.g. to indicate that a class promises to be immutable).
Upvotes: 0
Reputation: 110171
Does the C# 4.0 dynamic keyword get you out of jail (mostly) free? After all - you are already doing the type checking.
interface IC : IA, IB { }
void bar(object obj)
{
if (obj is IA && obj is IB)
{
IC x = (dynamic)obj;
foo(x);
}
}
Does that break if foo tries to cast the parameter to T? I don't know.
Upvotes: 4
Reputation: 56507
Your bar
method should be generic too, with the same constraints as foo
.
But if you really want to solve this problem, you can create a wrapper for an object that implements both interfaces that delegates all calls to the wrapped instances:
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
}
And use it like this:
static void bar(object obj) {
if (obj is IA && obj is IB) {
foo(new ABWrapper((IA)obj));
}
}
Upvotes: 2
Reputation: 55195
It seems that you're misunderstanding how generics work: when calling a method which has a generic parameter T
, T
must be statically known at compile time. Although the compiler can sometimes infer it (and you therefore don't always need to explicitly write it down), some T
must be supplied when calling the method. In your case, all you know is that obj
is an IA
and an IB
, but this doesn't give you enough information to call foo<T>
, since you have no idea what T
ought to be. You'll either have to use reflection, cast to a specific type which implements both IA
and IB
, or make a more dramatic design change.
Upvotes: 6
Reputation: 27515
I agree with the other responders that you probably have a design issue if you need to do this, but you could accomplish it with a proxy object that implements both interfaces and delegates the calls to the two casted interface instances of the unknown Object. Now, when you call this method, you can construct the proxy for any type that supports both interfaces.
Upvotes: 4
Reputation: 60085
you will have to define third type (possibly interface) that inherits from both interfaces. if you have such constraints then definitely you should have one. otherwise it is unusable. if this (obj is IB && obj is IB)
then obj is exactly that type.
Upvotes: 0