tags:

views:

296

answers:

3

Is there any type-safe, compile-time checked possibilty of referring to values that implement multiple interfaces?

Given

interface A {
    void DoA();
}

interface B {
    void DoB();
}

I'm able to write code for objects implementing A or B, but not both. So I've to come up with ugly wrappers:

class ABCollection {
    private class ABWrapper : A, B {
        private readonly A a;
        private readonly B b;

        public static ABWrapper Create<T>(T x) where T : A, B {
            return new ABWrapper { a = x, b = x };
        }

        public void DoA() {
            a.DoA();
        }

        public void DoB() {
            b.DoB();
        }
    }

    private List<ABWrapper> data = new List<ABWrapper>();

    public void Add<T>(T val) where T : A, B {
        data.Add(ABWrapper.Create(val));
    }
}

Is there a trick to write this code more intuitively without losing type-safety (runtime-casts etc.)?

E.g.

private List<A and B> ...

Edit: This is not about having a list in particular - I just wanted to give a "complete" example with the issue of storing such values. My problem is just how to type a combination of both interfaces (like A & B or A and B).

Another more useful example: List<IDrawable & IMovable> ...

A: 

I'm not clear on why you'd want to do this. If you did, you could declare a base interface:

interface AorB {}

interface A : AorB {
    void DoA();
}

interface B : AorB {
    void DoB();
}

and store those in the collection. Of course you'd have to is- or as-cast when retrieving (standard extension methods could help here).

It seems to me that this is a possible violation of SRP, and the collection is doing too much. Alternately the interfaces are too finely-grained.

TrueWill
This won't solve it if both `A` and `B` are not interfaces defined by him, and there are classes also not defined by him (and thus unaware of the existence of `AorB` which implement them). For example, imagine that `A=IComparable`, `B=IFormattable`, and he wants to match anything that's both `IComparable` and `IFormattable` - such as `System.Int32`.
Pavel Minaev
@Pavel: Good point.
TrueWill
+6  A: 

You can do parametric polymorphism like that in C#, but not subtype polymorphism. That is, you can create a polymorphic method like:

void Foo<T>(T t) where T : IFoo, IBar
{
  t.Foo();
  t.Bar();
}

and then you must pass an object whose type is known at compile time to implement both IFoo and IBar.

But there is no way to say

void Foo(IFoo-and-IBar t) 
{
  t.Foo();
  t.Bar();
}

and then pass in a value that is both an IFoo and an IBar. Neat feature, but not one we support.

Eric Lippert
I don't think that was actually his question. I think he was wondering about the `List`.
Michael Myers
@Eric Lippert: Thanks. If the feature is not supported directly, is there a preferable way of coding a workaround?
Dario
@Dario: the wrapper that you wrote is about as good as it gets. You can make a generic class, but then you won't be able to expose the members on it.
Pavel Minaev
@Eric Lippert: Thanks
Dario
+1  A: 

Well, as Eric Lippert said, there's no IFoo-and-IBar type you can use as a method parameter type.

However, I was playing around with some ideas and came up with an alternate way of using your wrapper class that may be better. I'll leave that up to you (or whoever else might search for this question) to decide:

CLASSES

public abstract class ABWrapper : IA, IB
{
    private readonly IA a;
    private readonly IB b;

    protected ABWrapper( IA a, IB b ) { this.a = a; this.b = b; }

    // Implement methods on IA and IB
}

public sealed class ABWrapper<T> : ABWrapper
    where T : IA, IB
{
    private ABWrapper( T a, T b ) : base( a, b ) { }

    public static implicit operator ABWrapper<T>( T t )
    {
        if ( t == null ) return null;
        return new ABWrapper<T>( t, t );
    }
}

EXAMPLE

public class AB : IA, IB { }

void Method( ABWrapper x )
{
}

void Main()
{
    AB x = null;
    Method( (ABWrapper<AB>) x );
}

The icky thing about this is that you need to do a cast to ABWrapper<T> at every call site. You could also create an extension method ABWrapper ToABWrapper<T>(this T t) where T : IA, IB to replace the cast if that would be more preferable.

It would be cool if the compiler could reason that an implicit conversion from AB to ABWrapper exists via implicit conversions to and from ABWrapper<T>. There's probably a very good reason it doesn't try to do that, however.

However, what you gain is the ability to put ABWrapper all throughout your method parameters without needing to genercize the methods.

Tinister