papa_bear
papa_bear

Reputation: 145

'Strongly Typed' Generic Collections that hold any <T> of a given Interface/Class

Is it possible to declare a generic collection to hold only objects implementing a generic Interface with any <T>?

My question burns down to: If I want to/have to store objects implementing a generic interface, is there a better way to express that fact than using a non generic collection or (generic of <Object>).

Example:

// An example Generic Interface
interface ISyncInterface<T>
{
    Task DoSync();
    IEnumerable<T> NewItems { get; }
}

// a manager-class that registers different classes implementing
// the generic interface.
// The code works - can it be done better?
class Manager
{
    private List<Object> _services = new List<Object>(); // <- works but is basically non generic
    // however the RegisterService() ensures that only correct types can be added.

    // would like to have something like below to indicate the Interface-Type
    // however: this would only allow _services2.Add to hold types of ISyncInterface<Object>
    //    - ISyncInterface<ServiceA_DTO> would fail.
    private List<ISyncInterface<Object>> _services2 = new List<ISyncInterface<Object>>();

    void RegisterService<T, U>(T service)
        where T : ISyncInterface<U>
    {
        _services.Add(service); // <- works e.g. for SyncServiceA 

        // _services2.Add(service); // <- FAILS for SyncServiceA - no conversion
        // _services2.Add((ISyncInterface<Object>) service); // <- FAILS also - no explicit cast
    }
}

// SETUP - The classes used above. Just to clarify.
class ServiceA_DTO { }
class ServiceB_DTO { }

class SyncServiceA : ISyncInterface<ServiceA_DTO>
{
    public Task DoSync() {}
    public IEnumerable<ServiceA_DTO> NewItems { get; }
}

class SyncServiceB : ISyncInterface<ServiceB_DTO>
{
    public Task DoSync() {}
    public IEnumerable<ServiceB_DTO> NewItems { get; } 
}

Is this possible at all? Any advice is highly appreciated!

Update: New, more verbose code to clarify the problem.

Below there was a suggestion to base the generic interface on an non generic one. But as a consequence all implementing classes of the generic interface would have to implement the non generic methods, properties etc. - or is there a way around it?

Thanks for your input!

Upvotes: 0

Views: 613

Answers (2)

Eric Lippert
Eric Lippert

Reputation: 660014

Is it possible to declare a generic collection to hold only objects implementing a generic interface instantiated with any T?

Short answer: no.

Longer answer: no, because that is not useful.

Let's consider a simple generic interface:

interface I<T> { T Get(); }

And a bunch of objects that implement it:

class Lion : I<Lion> 
{
  public Lion Get() => this;
}
class TaxPolicyFactory : I<TaxPolicy>
{
  public TaxPolicy Get() => new TaxPolicy();
}
class Door: I<Doorknob>
{
  public Doorknob Get() => this.doorknob;
  ...
}

OK, now suppose you have a List<I<ANYTHING>> like you want:

var list = new List<I<???>> { new TaxPolicyFactory(), new Lion(), new Door() };

You've got a list with a tax policy factory, a lion and a door in it. Those types have nothing in common with each other; there's no operation you can perform on each of those objects. Even if you could call Get on each of them, then you'd have a sequence with a tax policy, a lion and a doorknob in it, and what are you going to do with that?

Nothing, that's what. The constraint "implements interface I<T> for any T" is simply not a useful constraint in C#, so there is no way to express it.

It sounds like you have an "XY" problem. That is a problem where you have a bad solution in mind, and now you are asking questions about your bad solution. Ask us a question about the real problem you have, not the bad idea you've got for its solution. What's the real problem?


UPDATE: With the new information in the question it is now much more clear. The feature you want is called generic interface covariance, which was my favourite feature for C# 4.

If you update your interface definition to

interface ISyncInterface<out T> { ... }

then you can use an ISyncInterface<String> in a context where an ISyncInterface<Object> is expected. For example, you could put an ISyncInterface<Giraffe> into a List<ISyncInterface<Animal>> or whatever.

However you are required to ensure that your interface definition only uses T in a covariantly valid position. Your interface is valid as stated, but if for example you ever want to add a method void M(T t); to your interface, it will no longer be covariantly valid. The "out" is a mnemonic telling you that T can only be used as output of methods. Since IEnumerable<T> is also covariantly valid, it's fine; there are no inputs of T in an IEnumerable<T>.

Also, variance only works with generic interfaces and delegates, and the varying types must be reference types. You can't put an ISyncInterface<int> into a List<ISyncInterface<Object>> because int is not a reference type.

There are many posts on SO about covariance and contravariance; you should also read the Microsoft documentation. It can be a confusing feature. If you're interested in the historical details of how we designed and implemented the feature, see my blog.

Upvotes: 3

Jonathan Alfaro
Jonathan Alfaro

Reputation: 4376

Perhaps you can try something like this:

  public interface MyInterface
  {//methods common to all types
    void FirstMethod();
  }
  public interface MyInterface<T> : MyInterface
  {//methods specific to a type
    void FirstMethod(T parameter);
  }
  public class MyClassThatHandlesAllInterfaces
  {
    private List<MyInterface> _allInterfacesT; //first interface in the chain
    public void AddInterface<T>(MyInterface<T> ifToAdd)
    {
      _allInterfacesT.Add(ifToAdd); // <- this is what I'd like to do
    }
  }

I use this pattern quite often. Because I do not know all the details about your scenario it might not be suitable for you.

But it might help other people searching google.

Upvotes: 0

Related Questions