确保泛型集合包含从两个基础对象派生的对象

时间:2013-06-11 05:41:11

标签: c# type-constraints

我有一个有趣的问题,我一直在盘旋,但我似乎从不安静找到解决方案。

我倾向于成为一名防御性程序员,因此我尝试编写可以防止问题发生的代码,而不是在问题发生后对问题作出反应。为此,我有以下情况。请使用以下代码:

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,其他接口是可选的。

4 个答案:

答案 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
}