C#接口,带有返回多个接口实现的方法

时间:2011-01-16 17:15:25

标签: c# interface

使用接口时,我经常遇到这样的情况:我希望确保属性或方法的返回值,或者有时是方法的参数,在不创建新接口的情况下实现两个或更多接口。

我现在的具体实例是,我想指定一个方法将导致IEnumerable<SomeType>也支持INotifyCollectionChanged - 这样使用该接口的另一个对象不需要进行类型转换,仍然可以访问这两个设置。 (我不想明确使用ReadOnlyObservableCollection,因为它只适用于ObservableCollection个对象,但我还想让选项打开,以便接口的未来实现者在需要时使用它。)

我认为这只能用方法的参数来处理,而不能通过提供如下方法声明来返回值:

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

为了清楚起见,我真正喜欢的是使用类无法指定确切返回类型的东西。与以下类似,但显然语法不起作用甚至没有意义。

(IEnumerable<string>, INotifyCollectionChanged) GetStringData()

关于我如何做到这一点的任何建议?或者,如果失败了,我怎么能取得同样的结果呢?

6 个答案:

答案 0 :(得分:2)

没有好的解决方案,但我最好的猜测是在界面中添加代码合约。仍然要求调用者将结果转换为他需要的接口。

类似的东西:

Contract.Ensures(Contract.Result<object>() is IEnumerable<string>);
Contract.Ensures(Contract.Result<object>() is INotifyCollectionChanged);

答案 1 :(得分:2)

我能看到这一切的唯一方法就是利用dynamic,因为它在运行时都得到了解决,例如:

public dynamic GetStringData()
{

}

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

但是您将失去编译器可能会遇到的所有类型安全性。我认为执行此操作的最佳方法是来制作复合界面。

public IComposite GetStringData()
{

}

IEnumerable<string> enumerable = GetStringData();

答案 2 :(得分:1)

您可以创建另一个抽象(适配器)来缓存返回值,并为您需要的不同类型提供单独的访问器。这样,客户端代码就不会受到测试和转换。您可以返回此适配器而不是原始返回值。

或者,您只需返回具有所需输出的tuple即可。

答案 3 :(得分:1)

选项1:强类型的out参数(也称为TryGetValue

这允许使用者选择他们想要的特定接口-即使它确实返回相同的值。

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

或者:

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

(我的重载使用不同的out参数名称,以便消费者选择带有显式参数名称标签的重载,而不是类型推断,这很痛苦。)

选项2:隐式类型转换

这受OneOf<T...>https://github.com/mcintyre321/OneOf)的启发。

将此类型添加到您的项目中:

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

所以您的SettingsStore现在是:

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 );
    }
}

由于implicit的工作方式,GetStringData的使用者可以这样使用它:

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 ) { }

选项3:只需定义一个新接口

  

我经常遇到这样的情况:我想确保从属性或方法的返回值,或者有时是方法的参数,实现两个或更多个接口,而又没有创建新接口。

我不知道您为什么反对定义新的接口类型,因为接口继承 是支持这种情况的惯用C#方法(因为C#尚不像TypeScript那样支持代数类型):

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

如果您担心接口是否处于狂野状态,并且希望添加其他接口(例如IReadOnlyList<String>),那么会破坏ABI兼容性,那么您可以这样做:

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

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

class SettingsStore
{
    public ISettingsStrings2 GetStringData();
}

较旧的SettingsStore.GetStringData()(声明的返回类型为ISettingsStrings的ABI使用者仍然可以工作,因为ISettingsStrings2实现了ISettingsStrings

答案 4 :(得分:0)

也许一般的方法可以吗?

T GetStringData<T>()

并为T

添加一些限制

答案 5 :(得分:0)

您对该方法将返回的具体对象类型了解多少?如果您知道从界面返回将是一个Widget,并且该Widget支持IEnumerable和INotifyCollectionChanged,您可以定义该函数以返回Widget。如果您知道返回类型将是您将为您指定的目的而设计的类,但您不确切知道它将是什么类,您可以定义一个新的接口INotifiableEnumerable,它从IEnumerable和INotifyCollectionChanged派生,并且你将要返回的任何类实现INotifiableEnumerable。请注意,在后一种情况下,您的函数将无法返回未显式实现INotifiableEnumerable的类,即使它们碰巧同时实现了IEnumerable和INotifyCollectionChanged。