使用接口时,我经常遇到这样的情况:我希望确保属性或方法的返回值,或者有时是方法的参数,在不创建新接口的情况下实现两个或更多接口。
我现在的具体实例是,我想指定一个方法将导致IEnumerable<SomeType>
也支持INotifyCollectionChanged
- 这样使用该接口的另一个对象不需要进行类型转换,仍然可以访问这两个设置。 (我不想明确使用ReadOnlyObservableCollection
,因为它只适用于ObservableCollection
个对象,但我还想让选项打开,以便接口的未来实现者在需要时使用它。)
我认为这只能用方法的参数来处理,而不能通过提供如下方法声明来返回值:
void SetStringData<T>(T data) where T : IEnumerable<string>, INotifyCollectionChanged
为了清楚起见,我真正喜欢的是使用类无法指定确切返回类型的东西。与以下类似,但显然语法不起作用甚至没有意义。
(IEnumerable<string>, INotifyCollectionChanged) GetStringData()
关于我如何做到这一点的任何建议?或者,如果失败了,我怎么能取得同样的结果呢?
答案 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)
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
参数名称,以便消费者选择带有显式参数名称标签的重载,而不是类型推断,这很痛苦。)
这受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 ) { }
我经常遇到这样的情况:我想确保从属性或方法的返回值,或者有时是方法的参数,实现两个或更多个接口,而又没有创建新接口。
我不知道您为什么反对定义新的接口类型,因为接口继承 是支持这种情况的惯用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。