2

Similiar to this question: How can I require a method argument to implement multiple interfaces? I want a method argument to implement several interfaces.

The interfaces shall be combinable in arbitrary fashion and I don't want to create an interface for each valid combination.

Think of a file. It can be:

  1. readable => IReadable
  2. writeable => IWriteable
  3. an archive => IArchive
  4. automatically generated => IGenerated

...

If I want to express that an argument needs to be an writable, generated archive I don't want to generate IWritableGeneratedArchive since there are too combinations and I want to use it with some existing classes I cannot modify.

Pseudocode:

void WriteTo( IWritable + IGenerated + IArchive file)
{
   //...
}
Community
  • 1
  • 1
Onur
  • 5,017
  • 5
  • 38
  • 54

5 Answers5

8

The solution I found here: How can I require a method argument to implement multiple interfaces? adjusted for C#.

Credits go to Michael Myers

internal interface IF1
{
    void M1();
}

internal interface IF2
{
    void M2();
}

internal class ClassImplementingIF1IF2 : IF1, IF2
{

    public void M1()
    {
        throw new NotImplementedException();
    }

    public void M2()
    {
        throw new NotImplementedException();
    }

}

internal static class Test
{

    public static void doIT<T>(T t) where T:IF1,IF2
    {
        t.M1();
        t.M2();
    }

    public static void test()
    {
        var c = new ClassImplementingIF1IF2();
        doIT(c);
    }
}
Community
  • 1
  • 1
Onur
  • 5,017
  • 5
  • 38
  • 54
3

A generic + constraint perhaps?

void WriteTo<T>( T file) where T : IWritable,IGenerated,IArchive
{
   //...
}
Justin Harvey
  • 14,446
  • 2
  • 27
  • 30
2

Use generic constraints:

void WriteTo<T>(T file) where T: IWritable, IGenerated, IArchive
{
   //...
}

See Constraints on Type Parameters (C# Programming Guide)

maximpa
  • 1,958
  • 13
  • 16
  • 1
    That approach is necessary when enforcing constraints at compile-time. It's important to note, however, that calling the routine will be difficult in cases where one has an object of unknown type which is expected to satisfy the constraints, but one doesn't know of any single type which would satisfy all the constraints, and from which the object would derive. Further, it's difficult to store objects of such types in fields in a way that allows them to later be passed to such methods. – supercat Oct 25 '12 at 16:41
  • @supercat That's a good point! I can think of no good way to store such objects richt now... – Onur Oct 26 '12 at 09:09
  • @Onur: There are ways of doing it, but it's hard. I've come up with a solution without Reflection, but it's clunky. Using Reflection to dynamically dispatch things would be reasonably easy, but slow. A major difficulty is that in many cases one would want to pass delegates which pointed to unbound generic functions (e.g. pass a delegate which could be used to call `ActOnThing(T param) where T:constraints`, without the supplier of the delegate having to know `T`), but there's no way to create and invoke such delegates. – supercat Oct 26 '12 at 16:12
  • @Onur: What one can do is define an interface something like `IActOnConstrained` with method `ActOnThing where TThing:Constraint1,Constraint2`. One can pass an instance of `IActOnConstrainted<...>` as one would the aforementioned open delegate, and the recipient can substitute the appropriate type when performing the callback, but none of the syntactic sugar that's available to create inline delegates is available when creating interface implementations. – supercat Oct 26 '12 at 16:16
  • @supercat: I think your `IActOnConstrained` is similiar to my `Holder` approach mentioned below. – Onur Oct 31 '12 at 11:07
0

I don't want to generate IWritableGeneratedArchive since there are too combinations

How are there too many combinations? Whatever number of potential combinations there are, you only need to make a combined interface for the ones you actually use as a method parameter, and at that point you're writing it all out anyway. In other words, it's actually not that much more work.

I want to use it with some existing classes I cannot modify.

An IWritableGeneratedArchive type can be passed to something that only wants an IArchive or IWritable. This would still work with anything else you already have.


But since you've already decided against this and it looks like you already invested a lot in this architecture, I wonder if you've seen Code Contracts. They look like they might do what you need.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 1
    The problem is that if an already-existing and unmodifiable class implements `IFoo` and `IBar`, but does not implement any interface that inherits both, there's no way to define an interface which implements both and which will be satisfied by that class. Changing one line in the class and recompiling it would suffice, but that's not always possible. – supercat Oct 25 '12 at 19:36
  • @supercat That's exactly why I wanted a solution. The other point is the combinatorial explosion if there are not kust 2 interfaces. You have to add 2^n-1-n interfaces if you want all combinations of n interfaces! Which means 26 extra interfaces for 5 existing interfaces! – Onur Oct 26 '12 at 09:13
  • @Joel I'll have a closer look at code contracts. They seam to be overkill for this problem. – Onur Oct 26 '12 at 09:14
  • @Onur: In practice, only a tiny fraction of combinations would ever really be useful. It's only useful to define an interface combining I1, I2, and I3 if there exists code which needs all three, and which needs to accept instances of classes which have no other common interface. In many cases, this situation can be handled using a "maybe" interface (i.e. an interface which has members which are usable on some instances but not others, and properties to say which members are usable on a given instance). I really wish interfaces could specify default implementations, since... – supercat Oct 26 '12 at 16:21
  • ...a class which implements `IBasicFoo` [and guarantees all the methods therein work] and `IMaybeComplicatedFoo` [which includes `IBasicFoo` as well as some unsupported interfaces] must define stubs for all the interface methods which it includes but does not support. – supercat Oct 26 '12 at 16:23
  • @supercat: Of course we only need to define the combinations of interfaces we really need. But if we plan to put the class in a library and give it to others, how can I know which combinations they need? – Onur Oct 31 '12 at 11:01
  • @Onur: You can't know, which is part of why "maybe" interfaces can be a good thing. Another approach which can sometimes work is to define an interface `ISelf` with a single property `Self` of type `TSelf` which is expected to return itself, and derive from the interfaces you'll be using a variation which includes a `TSelf` parameter and inherits `ISelf`. For example, define interface `IFoo:IFoo,ISelf` and `IBar:IBar,ISelf`. Then if class `Wow` implements both `IFoo` and `IBar`, declare it as implementing IBar`, `IFoo`, and `ISelf`. – supercat Oct 31 '12 at 16:55
  • @Onur: Because of interface covariance, `Wow` may be cast not just the to interfaces mentioned, but also `IBar>`, `IFoo>`, and any other combination of those interfaces one can think of. If one casts it to `IBar>` and stores it in `It`, then `It` may be used as `IBar`, `It.Self` may be used as `IFoo`, and `It.Self.Self` may be used as `Wow`. This approach generalizes to any number of interfaces, but I've never seen anyone else do it. – supercat Oct 31 '12 at 16:57
  • @Onur: I think my biggest qualm with this approach is that while one may expect any object which implements `ISelf` to simply return a reference to itself, there's no guarantee that it will do so. If a class's implementation of `ISelf` returns itself, that would imply that the object implements `IBar`, and thus casting the object to `IBar` would succeed, but nothing would stop an object from implementing `ISelf` without implementing `IBar`. – supercat Oct 31 '12 at 17:00
  • @supercat: I think this approach looks too "unfamiliar" for most programmers to be easily usable ;-). – Onur Oct 31 '12 at 20:23
  • @Onur: It is a bit icky, though if there were a standard `ISelf` built into the Framework, the implementation cost for classes to support it would be really low. Unfortunately, without it in he Framework, widespread adoption of the technique would result in many projects having multiple essentially-identical-but-not-compatible versions of `ISelf`. Still, it probably wouldn't cost much for classes to implement the `ISelf` pattern, and it may sometimes be useful if code needs an unanticipated combination of interfaces. – supercat Oct 31 '12 at 20:36
0

This solution tries to incorporate supercat's idea with storing such "multi interface" objects.

I've done this by storing them in a Holder class that exposes the stated interfaces of the object.

Holder objects can be stored and are the expected argument. The downside is that you have to create the Holder first and that the order of Holder's type arguments matters.

On the plus side though you can also dynamically create Holders and of course store "multi interface" objects.

It would have been nice to enable the holder.Get<T>() methods (nicer than holder.t1) but it won't compile. Maybe someone has an idea how to fix it? I think it needs to add the constraint T1 is not T2 and vice versa. This seams to be related to C# generic does *not* implement something and they didn't find a solution.

internal interface IF1
{
    void M1();
}

internal interface IF1_extension : IF1
{
}

internal interface IF2
{
    void M2();
}

internal class ClassImplementingIF1IF2 : IF1_extension, IF2
{

    public void M1()
    {
        throw new NotImplementedException();
    }

    public void M2()
    {
        throw new NotImplementedException();
    }

}

internal interface Getter<T> where T : class
{
    T Get();
}

internal class Holder<T1, T2> //: Getter<T1>, Getter<T2> // not possible since T1 and T2 may be the same => won't compile!
    where T1 : class
    where T2 : class
{
    private Holder(T1 t1, T2 t2)
    {
        Debug.Assert(t1 != null, "Argument is no " + typeof(T1).Name);
        Debug.Assert(t2 != null, "Argument is no " + typeof(T2).Name);
        this.t1 = t1;
        this.t2 = t2;
    }

    public static Holder<T1, T2> CreateFrom<T>(T t) where T : T1, T2
    {
        return new Holder<T1, T2>(t, t);
    }
    public static Holder<T1, T2> CreateDynamicallyFrom(object t)
    {
        return new Holder<T1, T2>(t as T1, t as T2);
    }

    public readonly T1 t1;
    public readonly T2 t2;

    //T1 Getter<T1>.Get()
    //{
    //    return t1;
    //}
    //T2 Getter<T2>.Get()
    //{
    //    return t2;
    //}
}

internal class Holder<T1, T2, T3>  // Holder<T1,T2,T3,T4> etc. are defined in a similar way
    where T1 : class
    where T2 : class
    where T3 : class
{
    private Holder(T1 t1, T2 t2, T3 t3)
    {
        Debug.Assert(t1 != null, "Argument is no " + typeof(T1).Name);
        Debug.Assert(t2 != null, "Argument is no " + typeof(T2).Name);
        Debug.Assert(t3 != null, "Argument is no " + typeof(T3).Name);
        this.t1 = t1;
        this.t2 = t2;
        this.t3 = t3;
    }

    public static Holder<T1, T2,T3> CreateFrom<T>(T t) where T : T1, T2, T3
    {
        return new Holder<T1, T2, T3>(t, t, t);
    }
    public static Holder<T1, T2, T3> CreateDynamicallyFrom(object t)
    {
        return new Holder<T1, T2, T3>(t as T1, t as T2, t as T3);
    }

    public readonly T1 t1;
    public readonly T2 t2;
    public readonly T3 t3;

}


internal static class Test
{

    public static void doIt<T>(T t) where T : IF1, IF2
    {
        t.M1();
        t.M2();
    }

    public static void doIt(Holder<IF1, IF2> t) // Interfaces should be mentioned in alpahbetical order since Holder<IF1,IF2> != Holder<IF2,IF1>
    {
        t.t1.M1();
        t.t2.M2();
    }


    public static void doIt_extended<T1, T2>(Holder<T1, T2> t) // handles conversions from Holder<T1,T2> to Holder<T1 or base of T1, T2 or base of T2>
        where T1 : class, IF1
        where T2 : class, IF2
    {
        t.t1.M1();
        t.t2.M2();
    }

    public static void test()
    {
        var c = new ClassImplementingIF1IF2();
        doIt(c);
        var c_holder = Holder<IF1, IF2>.CreateFrom(c);
        doIt(c_holder);

        var another_c_holder = Holder<IF1_extension, IF2>.CreateFrom(c);
        doIt_extended(another_c_holder);


        object diguised_c = c;
        var disguised_c_holder = Holder<IF1, IF2>.CreateDynamicallyFrom(diguised_c);
        doIt(disguised_c_holder);

    }
}
Community
  • 1
  • 1
Onur
  • 5,017
  • 5
  • 38
  • 54
  • 1
    You need to have an abstract `HolderBase` and a concrete `Holder : HolderBase where T:T1,T2` which holds the object in a field `it` of type `T`. A `Holder` will then be able to pass that field to methods which take a generic type parameter constrained to `T1` and `T2`. If one will have a number of `HolderBase` references and want to pass them to `Foo(T param) where T:T1,T2`, one may include in `HolderBase` an abstract method `InvokeFoo`. Then, in `Holder`, override that method... – supercat Oct 31 '12 at 16:46
  • 1
    ...to call `Foo(it)`. If one can list in `HolderBase` all the methods one will need to call, that may be the simplest approach. Otherwise, one can use interfaces to describe the methods one will want to call, but that's a little clunkier. – supercat Oct 31 '12 at 16:48
  • This approach looks really interesting. Especially since the "user" can implement the `Holder` class and add the methods he needs. This reliefs the library programmer from guessing what might be needed. – Onur Oct 31 '12 at 20:29
  • The `Holder` and `HolderBase` would likely be declared consecutively in the same source file, with the latter simply adding a field of type `T` and overriding abstract methods to act upon it. If you want to allow calling of methods not explicitly provided for within `HolderBase` there are ways of doing it, but they're ugly. – supercat Oct 31 '12 at 20:38