Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typing polymorphic values with multiple interfaces in C#

Tags:

c#

interface

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> ...

like image 758
Dario Avatar asked Dec 10 '09 16:12

Dario


People also ask

Can polymorphism occur with interfaces?

Interfaces allow us to define polymorphism in a declarative way, unrelated to implementation. Two elements are polymorphic with respect to a set of behaviors if they realize the same interfaces.

Can a class inherit from multiple interfaces?

Simply put, in Java, a class can inherit another class and multiple interfaces, while an interface can inherit other interfaces.

Can a class inherit multiple interfaces in C#?

A class or struct can implement multiple interfaces, but a class can only inherit from a single class.

Can AC class implement multiple interfaces?

C# allows that a single class can implement multiple interfaces at a time, and also define methods and variables in that interface.


2 Answers

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.

like image 121
Eric Lippert Avatar answered Oct 03 '22 01:10

Eric Lippert


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.

like image 27
Tinister Avatar answered Oct 03 '22 01:10

Tinister