Ensuring a generic collection contains objects that derive from two base objects

StackOverflow https://stackoverflow.com/questions/17037202

  •  31-05-2022
  •  | 
  •  

Вопрос

I have an interesting problem that I keep circling around, but I never seem to quiet find a solution.

I tend to be a defensive programmer, so I try to write code that prevents problems from happening rather than reacting to problems once they've occurred. To that end, I have the following situation. Take the following code:

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

I need to pass a list of objects that derive from Base and implement IBase to a collection, and I need to make sure that only objects that have both are added to the list. Additionally, there can be an arbitrary number of classes that have both, so I cannot use the derived classes as constraints.

If I make the list of type Base, then I could add a Y object. If I make it of type IBase, then objects of type X can be added (neither of which are permitted).

I could, of course create my own generic collection class that has both types and has constraints for both. But, I don't want to have to do this for all possible collection types, and it's a lot of effort to duplicate all that functionality (even if you just forward the method calls to a contained class).

I could also create a BaseWithIBase class, which derives from both Base and IBase, and use that as my collection type, but I really don't want to force another abstraction if I don't have to.

I don't want this to be a runtime check, so walking the tree and throwing exceptions is not acceptable.

Can anyone suggest a better approach to this problem?

NOTE: Base and IBase are not related, just pointing out they are both base items of different types.

EDIT:

It seems that everyone wants to insist that "you don't need to do that" and that it's "not OOP". Nothing could be further from the truth. I was attempting to remove the specific from the question to prevent these kinds of questions and comments, so I will include my real situation.

The code is an implement of a Windows Service framework, based on the .NET Frameworks ServiceProcess.ServiceBase class. I am adding my own framework on top of this, that is intended to be heavily Dependency Injection based, and highly testable.

The collection must contain objects that derive from both ServiceBase and IService. IService is my framework extension that is used in my code, and for testing. It is basically just this:

public interface IService 
{
    void Start();
    void Stop();
}

In addition, I have a number of other interfaces:

public interface IRestartableService
{
    void Restart();
}

public interface IConfigurableService
{
    void Configure();
}

etc.. etc.. and a service may look like this:

public class MyService : ServiceBase, IService, IConfigurableService {}

My code requires IService, Windows requires ServiceBase, thus both are needed because I work with IService, and windows works with ServiceBase. I only require IService, the other interfaces are optional.

Это было полезно?

Решение

You can create your own wrapper collection simply:

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

By using a generic method with both constraints, you can be sure that Add can only be called with an appropriate type argument.

You could have two methods to expose the data as IEnumerable<T> - one returning IEnumerable<IBase> (using Cast<T>) and one returning IEnumerable<Base>... that would let you use LINQ on either type, but not both at the same time of course.

I suspect you may find this awkward elsewhere, however - you may find yourself littering your code with generic methods which you wouldn't typically need. While there may well be a good reason for wanting both the class part and the interface part, it would be worth taking a step back and considering whether they're really both necessary. Is there something extra you could add to the interface so that you could do away with the class constraint, for example?

Другие советы

There is no good answer to your question because the design itself is not really fitting OOP as implemented in C#/.NET.

If you absolutely need a collection where each element statically provides two independent interfaces, either a wrapper collection or some wrapper class like Wrapper<TFirst, TSecond, T> : IBoth<TFirst, TSecond> would solve your problem.

Example:

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

However the real question is why do you need that. Not to say that standard OOP model is perfect, but quite often a problem can be solved much easier if original design decisions are reviewed.

Another option is to completely ignore ServiceBase in most of the code and create a ServiceBaseAdapter for communication with the code that is not interface friendly. Such adapter can just call your interface methods when its method are called.

Try something like this:

List<object> collection = new List<object>();
foreach(var obj in collection.OfType<Base>().OfType<IBase>())
{
// Do what ever you want
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top