我有一个有趣的问题,我一直在盘旋,但我似乎从不安静找到解决方案。
我倾向于成为一名防御性程序员,因此我尝试编写可以防止问题发生的代码,而不是在问题发生后对问题作出反应。为此,我有以下情况。请使用以下代码:
public class Base {}
public Interface IBase {}
public class Derived : Base, IBase {}
public class Derived2 : Base, IBase {}
...
public class DerivedN : Base, IBase {}
public class X : Base {}
public class Y : IBase {}
我需要传递一个从Base派生的对象列表,并将IBase实现到一个集合,我需要确保只将具有两者的对象添加到列表中。另外,可以有任意数量的类同时具有这两者,因此我不能将派生类用作约束。
如果我创建Base类型列表,那么我可以添加一个Y对象。如果我使用IBase类型,则可以添加类型为X的对象(两者都不允许)。
我当然可以创建我自己的泛型集合类,它具有两种类型并且对两者都有约束。但是,我不想为所有可能的集合类型执行此操作,并且复制所有功能需要付出很多努力(即使您只是将方法调用转发给包含的类)。
我还可以创建一个BaseWithIBase类,它派生自Base和IBase,并将其用作我的集合类型,但如果我不需要,我真的不想强制另一个抽象。
我不希望这是运行时检查,因此不能接受树和抛出异常。
有人能建议更好地解决这个问题吗?
注意:Base和IBase没有关联,只是指出它们都是不同类型的基础项。
修改
似乎每个人都想坚持“你不需要这样做”而且它“不是OOP”。没有东西会离事实很远。我试图从问题中删除特定内容以防止这些问题和评论,所以我将包括我的实际情况。
代码是基于.NET Frameworks ServiceProcess.ServiceBase类的Windows Service框架的实现。我在此基础上添加了自己的框架,这是基于依赖注入的,并且可以高度测试。
集合必须包含从ServiceBase和IService派生的对象。 IService是我的框架扩展,用于我的代码和测试。基本上就是这样:
public interface IService
{
void Start();
void Stop();
}
此外,我还有许多其他接口:
public interface IRestartableService
{
void Restart();
}
public interface IConfigurableService
{
void Configure();
}
等..等等。服务可能如下所示:
public class MyService : ServiceBase, IService, IConfigurableService {}
我的代码需要IService,Windows需要ServiceBase,因此我需要两者,因为我使用IService,并且Windows与ServiceBase一起使用。我只需要IService,其他接口是可选的。
答案 0 :(得分:8)
您可以简单地创建自己的包装集合:
// TODO: Work out which collection interfaces you want to implement
public class BaseList
{
// Or use List<IBase>, if that's how you'll be using it more often.
private List<Base> list = new List<Base>();
public void Add<T>(T item) where T : Base, IBase
{
list.Add(item);
}
}
通过对两个约束使用泛型方法,可以确保只能使用适当的类型参数调用Add
。
您可以使用两种方法将数据公开为IEnumerable<T>
- 一个返回IEnumerable<IBase>
(使用Cast<T>
),另一个返回IEnumerable<Base>
...可以让您使用LINQ on 类型,但当然不是两者兼而有之。
我怀疑你可能会在其他地方发现这种尴尬 - 但是你可能会发现自己在使用通常不需要的通用方法乱丢代码。虽然很可能有充分的理由同时想要课程部分和界面部分,但是值得退后一步并考虑它们是否真的是必要的。是否有额外的东西可以添加到界面中,以便您可以取消类约束,例如?
答案 1 :(得分:0)
你的问题没有很好的答案,因为设计本身并不适合在C#/ .NET中实现的OOP。
如果你绝对需要一个集合,其中每个元素静态地提供两个独立的接口,那么包装器集合或像Wrapper<TFirst, TSecond, T> : IBoth<TFirst, TSecond>
这样的包装器类将解决你的问题。
示例:
public interface IBoth<TFirst, TSecond> {
TFirst AsFirst();
TSecond AsSecond();
}
public class Wrapper<T, TFirst, TSecond> : IBoth<TFirst, TSecond>
where T : TFirst, TSecond
{
private readonly T _value;
public Wrapper(T value) {
_value = value;
}
public TFirst AsFirst() {
return _value;
}
public TSecond AsSecond() {
return _value;
}
}
但真正的问题是你为什么需要这个。并不是说标准OOP模型是完美的,但如果对原始设计决策进行审查,通常可以更容易地解决问题。
答案 2 :(得分:0)
另一个选择是在大多数代码中完全忽略ServiceBase
并创建ServiceBaseAdapter
以便与非接口友好的代码进行通信。这样的适配器可以在调用其方法时调用您的接口方法。
答案 3 :(得分:-2)
尝试这样的事情:
List<object> collection = new List<object>();
foreach(var obj in collection.OfType<Base>().OfType<IBase>())
{
// Do what ever you want
}