Elliott Ding
Elliott Ding

Reputation: 61

Generic interface parameters in a generic interface

I have the following generic interface:

public interface IContainer<T>

and a generic interface that acts as a container for these generic interfaces:

public interface IGroupOfContainers<TContainer, TValue>
    where TContainer : IContainer<TValue>

Now I can define some classes:

public class SomeContainer<T> : IContainer<T>
...

public class OtherContainer<T> : IContainer<T>
...

public class SomeGroupOfContainers<TContainer, TValue>
    : IGroupOfContainers<TContainer, TValue>
    where TContainer : IContainer<TValue>
...

public class OtherGroupOfContainers<TContainer, TValue>
    : IGroupOfContainers<TContainer, TValue>
    where TContainer : IContainer<TValue>
...

Now I want IntGroupManager to be able to work with any object that complies with the IGroupOfContainers<IContainer<int>, int> interface:

public class IntGroupManager
{
    public IGroupOfContainers<IContainer<int>, int> IntGroup { get; set; }
    ...
}

But when I try:

var manager= new IntGroupManager();
manager.IntGroup = new GroupOfContainers<Container<int>, int>
    as IGroupOfContainers<IContainer<int>, int>;

I get a "Suspicious cast: there is no type which is inherited from both GroupOfContainers<Container<int>, int> and IGroupOfContainers<IContainer<int>, int>" warning, and when I attempt to run the code, manager.IntGroup is set to null, as the cast failed.

If I change the type signature of the IntGroup property to

public IGroupOfContainers<Container<int>, int> IntGroup { get; set; }

(changing IContainer to the concrete class Container), the code executes properly. However, I then wouldn't be able to instantiate an instance of IntGroupManager with an IntGroup of type, say, GroupOfContainers<OtherContainer<int>, int>, since OtherContainer is not derived from Container.

I could introduce generic parameters into the definition of IntGroupManager, perhaps rewriting it as

public class IntGroupManager<TContainer> where TContainer : IContainer<int>

However, this would bind any instance of IntGroupManager to a specific type of IContainer; I wouldn't be able to do

var manager = new IntGroupManager<Container<int>>();

// Set manager.IntGroup and do some things with it
// This is okay
manager.IntGroup = new GroupOfContainers<Container<int>, int>();
// manager executes some code involving IntGroup and manipulates its internal state

// Set manager.IntGroup to something else and do things with it
// This is not okay, since OtherContainer<int> is not derived from Container<int>
manager.IntGroup = new GroupOfContainers<OtherContainer<int>, int>();

and so I lose some flexibility.

How can I properly define the IntGroup property to be of any type conforming to IGroupOfContainers<IContainer<int>, int>? I want to be able to use the same instance of IntGroupManager with any IntGroup object that conforms to IGroupOfContainers<IContainer<int>, int>. Is there a way to do this?

Upvotes: 1

Views: 166

Answers (2)

Mehmet Ataş
Mehmet Ataş

Reputation: 11559

Though it is hard to say without seeing how you used TContainer and TValue (input or output?) but, have you tried using out keyword for the generic type arguments? For details you can check Covariance and Contravariance

public interface IContainer<out T>

public interface IGroupOfContainers<out TContainer, TValue>
    where TContainer : IContainer<TValue>

Upvotes: 1

Heinzi
Heinzi

Reputation: 172468

This is an answer to your question in the comments of Mehmet's answer (sorry, too long for a comment):

If it were either input or output, and not both, [covariance] would be the solution, right? Is there any way to do this even if IGroupOfContainers defined functions that used TContainer as both input and output?

Let's continue with your example and assume that the following statement works:

var g = new GroupOfContainers<Container<int>, int>
    as IGroupOfContainers<IContainer<int>, int>;

Let's assume that IGroupOfContainers<TContainer, TValue> has a method AddContainer(TContainer):

g.AddContainer(new OtherContainer<int>());

This would compile, since OtherContainer<int> is an IContainer<int>.

However, you just added an OtherContainer to a group of Containers! That's why you cannot do what you want to do unless you prevent IGroupOfContainers from taking TContainer inputs.

Upvotes: 2

Related Questions