3

When working with interfaces, I frequently run into the case where I want to ensure that a return value from a property or method, or sometimes a parameter to a method, implements TWO or more interfaces without creating a new interface.

My specific instance right now is that I want to specify that a method will result in a IEnumerable<SomeType> that also supports INotifyCollectionChanged - that way another object using the interface does not have to typecast and can still access both settings. (I don't want to use ReadOnlyObservableCollection explicitly because it only works well with ObservableCollection objects, but I would also like to leave the option open for future implementers of the interface to use it if they want to.)

I'm thinking that this can only be handled with parameters to a method, not return values, by providing a method declaration as follows:

void SetStringData<T>(T data) where T : IEnumerable<string>, INotifyCollectionChanged

Just to be clear, what I'd really like is something where the using class CAN'T specify the exact return type. Similar to the following, but obviously the syntax doesn't work or even make sense.

(IEnumerable<string>, INotifyCollectionChanged) GetStringData()

Any suggestions on how I can do this? Or, failing that, how I can achieve the same results?

Matt DeKrey
  • 11,582
  • 5
  • 54
  • 69

6 Answers6

3

The only way I can see this being done is making use of of dynamic as its all resolved at runtime, e.g.:

public dynamic GetStringData()
{

}

And:

IDisposable disposable = GetStringData();
ISomeOtherInterface other = GetStringData();

BUT you lose all type safety that the compiler would fall over on. I think the best way to do this is to make a composite interface.

public IComposite GetStringData()
{

}

And:

IEnumerable<string> enumerable = GetStringData();
Matthew Abbott
  • 60,571
  • 9
  • 104
  • 129
  • Yeah, this does lose all type-safety - I'd rather just return explicitly the `IEnumerable` and let them test for `INotifyCollectionChanged`. Still, +1 for a well thought-out-answer. Thanks! – Matt DeKrey Jan 16 '11 at 18:00
2

No good solution, but my best guess is adding a code contract to the interface. Still requires that the caller casts the result the the interface he needs.

Something like:

Contract.Ensures(Contract.Result<object>() is IEnumerable<string>);
Contract.Ensures(Contract.Result<object>() is INotifyCollectionChanged);
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • Hmm, I really like this - I was only aware of how to do this with a class. But with the `ContractClassAttribute` (http://stackoverflow.com/questions/2150983) I can specify it on an interface. Thanks! (I still hope they add an explicit solution to the C# standard eventually...) – Matt DeKrey Jan 16 '11 at 17:59
1

You can create another abstraction (an adapter) that caches the return value and provides separate accessors for the different types that you need. This way, client code is shielded from testing and casting. You can return this adapter instead of the original return value.

Or, you can just return a tuple with your desired outputs.

Jordão
  • 55,340
  • 13
  • 112
  • 144
  • Hmm, it does give me the type safety AND allows previous classes by writing a simple wrapper. I wonder why I hadn't thought of it before. Unfortunately, this does feel a bit clunky as far as ease-of-use goes... I'll keep it in mind for the next time I'm implementing such a requirement. Thanks! – Matt DeKrey May 02 '11 at 04:12
  • Reflecting on this, I came to realize that what I'm suggesting is a specialized tuple. I'll update the answer with that. – Jordão May 02 '11 at 14:59
1

Option 1: Strongly-typed out parameters (aka TryGetValue)

This allows the consumer to select the specific interface they want - even if it does return the same value.

class SettingsStore
{
    public Boolean TryGetStringData( out IEnumerable<String> dataEnumerable ); 
    public Boolean TryGetStringData( out INotifyCollectionChanged dataCollection );
}

Or:

class SettingsStore
{
    public void GetStringData( out IEnumerable<String> dataEnumerable ); 
    public void GetStringData( out INotifyCollectionChanged dataCollection );
}

(My overloads use different out parameter names to allow consumers to select overloads with explicit parameter name labels instead of type-inference which can be painful).

Option 2: Implicit type conversion

This is inspired by OneOf<T...> ( https://github.com/mcintyre321/OneOf ).

Add this type to your project:

static class ImplOf
{
    public static ImplOf<TImpl,T1,T2>( TImpl implementation )
        where TImpl : T1, T2
    {
        return new ImplOf<T1,T2>( implementation );
    }

    public static ImplOf<TImpl,T1,T2,T3>( TImpl implementation )
        where TImpl : T1, T2, T3
    {
        return new ImplOf<T1,T2,T3>( implementation );
    }

    // etc for 4, 5, 6+ interfaces.
}

struct ImplOf<T1,T2>
{
    private readonly Object impl;
    public ImplOf( Object impl ) { this.impl = impl; }

    public static implicit operator T1(ImplOf<T1,T2> self) => (T1)self.impl;
    public static implicit operator T2(ImplOf<T1,T2> self) => (T2)self.impl;

    public static implicit operator ImplOf<T1,T2>(T1 impl) => new ImplOf<T1,T2>( impl );
    public static implicit operator ImplOf<T1,T2>(T2 impl) => new ImplOf<T1,T2>( impl );

    // These properties are for convenience and are not required.
    public T1 Interface1 => (T1)this.impl;
    public T2 Interface2 => (T2)this.impl;
}

struct ImplOf<T1,T2,T3>
{
    private readonly Object impl;
    public ImplOf( Object impl ) { this.impl = impl; }

    public static implicit operator T1(ImplOf<T1,T2,T3> self) => (T1)self.impl;
    public static implicit operator T2(ImplOf<T1,T2,T3> self) => (T2)self.impl;
    public static implicit operator T3(ImplOf<T1,T2,T4> self) => (T3)self.impl;

    public static implicit operator ImplOf<T1,T2,T3>(T1 impl) => new ImplOf<T1,T2,T3>( impl );
    public static implicit operator ImplOf<T1,T2,T3>(T2 impl) => new ImplOf<T1,T2,T3>( impl );
    public static implicit operator ImplOf<T1,T2,T3>(T3 impl) => new ImplOf<T1,T2,T3>( impl );

    public T1 Interface1 => (T1)this.impl;
    public T2 Interface2 => (T2)this.impl;
    public T3 Interface2 => (T3)this.impl;
}

// etc for 4, 5, 6+ interfaces

So your SettingsStore is now:

public class SettingsStore
{
    public ImplOf<IEnumerable<String>,INotifyPropertyChanged> GetStringData()
    {
        MyStringDataCollection collection = ... // `MyStringDataCollection` implements both `IEnumerable<String>` and `INotifyPropertyChanged`.

        return ImplOf.Create<MyStringDataCollection,IEnumerable<String>,INotifyPropertyChanged>( collection );
    }
}

Because of how implicit works, consumers of GetStringData can use it like so:

IEnumerable<String> strings = store.GetStringData();

INotifyCollectionChanged collection = store.GetStringData();

// Or they can use ImplOf directly, but need to use the `InterfaceN` properties:
var collection = store.GetStringData();
foreach( String item in collection.Interface1 ) { }

Option 3: Just define a new interface

I frequently run into the case where I want to ensure that a return value from a property or method, or sometimes a parameter to a method, implements TWO or more interfaces without creating a new interface.

I don't know why you're opposed to defining a new interface type because interface inheritance is the idiomatic C# way of supporting this scenario (because C# does not yet support algebraic types like TypeScript does):

interface ISettingsStrings : IEnumerable<String>, INotifyCollectionChanged
{
    // No interface body is required.
}

If you're concerned about this breaking ABI compatibility if your interface is in-the-wild and you want to add additional interfaces (e.g. IReadOnlyList<String>) then you can just do this:

interface ISettingsStrings : IEnumerable<String>, INotifyCollectionChanged
{
    // No interface body is required.
}

// Declare a successor interface:
interface ISettingsStrings2 : ISettingsStrings, IReadOnlyList<String>
{
}

class SettingsStore
{
    public ISettingsStrings2 GetStringData();
}

ABI consumers of the older SettingsStore.GetStringData() (which had a declared return type of ISettingsStrings) will still work because ISettingsStrings2 implements ISettingsStrings.

Dai
  • 141,631
  • 28
  • 261
  • 374
  • Option 2 is an interesting solution. At the time, I was dealing with framework classes (ObservableCollection) but not wanting to hard-code to them, as occasionally I had reason to implement my own. At this point, I'd probably use my (originally non-working) tuple return value or your `ImplOf<>` examples adding a Deconstruct method, though I haven't run into this issue again for some time! – Matt DeKrey Dec 20 '19 at 16:55
0

Perhaps a generic method would do?

T GetStringData<T>()

And add some restrictions to T

Emond
  • 50,210
  • 11
  • 84
  • 115
  • That requires the caller to know the type `T`. So it doesn't satisfy "where the using class CAN'T specify the exact return type". – CodesInChaos Jan 16 '11 at 17:20
  • In that case: No, you can't. the calling code has to know what is being returned unless you're ok with object or another base class. – Emond Jan 16 '11 at 17:23
0

What do you know about the concrete type of the objects the method will be returning? If you know that the return from the interface will be a Widget, and that Widget supports IEnumerable and INotifyCollectionChanged, you could define the function to return Widget. If you know that the return type will be a class that you'll be designing for your indicated purpose, but you don't know exactly what class it will be, you could define a new interface INotifiableEnumerable which derives from both IEnumerable and INotifyCollectionChanged, and have any class you'll be returning implement INotifiableEnumerable. Note that in the latter case it will not be possible for your function to return classes that don't explicitly implement INotifiableEnumerable even if they happen to implement both IEnumerable and INotifyCollectionChanged.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Unfortunately, I explicitly want to be able to return an existing class, namely, `ReadOnlyObservableCollection`, but also allow for similar objects that aren't tied to `ObservableCollection`. I agree, if there were some other base class or interface, I would have used that... but that's sorta why I asked the question. Thanks for taking the time to answer! – Matt DeKrey May 02 '11 at 04:13