Generic List of Generic Interfaces not allowed, any alternative approaches?
Asked Answered
S

4

32

I am trying to find the right way to use a Generic List of Generic Interfaces as a variable.

Here is an example. It is probably not the best, but hopefully you will get the point:

public interface IPrimitive<T>
{
     T Value { get; }
}

and then in another class, I want to be able to declare a variable that holds a list of objects that implement IPrimitive<T> for arbitrary T.

// I know this line will not compile because I do not define T   
List<IPrimitive<T>> primitives = new List<IPrimitives<T>>;

primitives.Add(new Star());   // Assuming Star implements IPrimitive<X>
primitives.Add(new Sun());    // Assuming Sun implements IPrimitive<Y>

Note that the T in IPrimitive<T> could be different for each entry in the list.

Any ideas on how I could setup such a relationship? Alternative Approaches?

Spectroradiometer answered 23/12, 2010 at 21:23 Comment(1)
great question, took a while to dig this out. cross-language link for those who came from java: Java using generics with lists and interfacesClinandrium
H
33
public interface IPrimitive
{

}

public interface IPrimitive<T> : IPrimitive
{
     T Value { get; }
}

public class Star : IPrimitive<T> //must declare T here
{

}

Then you should be able to have

List<IPrimitive> primitives = new List<IPrimitive>;

primitives.Add(new Star());   // Assuming Star implements IPrimitive
primitives.Add(new Sun());    // Assuming Sun implements IPrimitive
Hubble answered 23/12, 2010 at 21:28 Comment(5)
By having a list of IPrimitive instead of IPrimitive<T> would not allow you to get at Value from the items in the list since Value is defined in IPrimitive<T> and not IPrimitive.Spectroradiometer
@P B, using this methodology, IPrimitive would have a property defined as object Value { get; } and then Star would need provide implementations for both IPrimitive<T> and IPrimitive. Such as: public int Value { get { return _value; } } to implement IPrimitive<int> and object IPrimitive.Value { get { return this.Value; } } to take care of the non-generic interface explicitly.Boil
@user414076 but this will let you introduce interfaces / signatures you wanted to avoid using the generic intially! VisualStudio will let me know, that IPrimitive.Value (object Value { get; set; }) is hidden by IPrimitive<T>.Value (T Value { get; set; }) and I should use the new keyword if hiding was intended. Doing so (new T Value { get; set; }), still Star is forced to implement both interfaces ;/Clinandrium
But...but... the sun is a star!Actinomycete
I can't call the method T Value { get; } Hagfish
C
11

John is correct.

Might I also suggest (if you are using C# 4) that you make your interface covariant?

public interface IPrimitive<out T>
{
     T Value { get; }
}

This could save you some trouble later when you need to get things out of the list.

Coastline answered 23/12, 2010 at 21:29 Comment(3)
T will be defined, but it is defined in each object that implements T. So in the example above it will be defined in Star and in Sun. So when I go to create the Generic List in another class, it does not know what T is. T could be a different type in Star and SunSpectroradiometer
@Andrew: I see that he clarified in comments that he needs potentially a different T for each list item. In that case, the answer by Josh. In any case, I'm thinking I should delete my answer, but yours should be edited to point to the remaining answer.Luigi
Microsoft explains in this page Variance in Generic Interfaces : Please take a look, it could help greatly : Variance in Generic Interfaces CollectionsChevet
L
3

You say it won't work because you don't define T. So define it:

public class Holder<T>
{
    public List<IPrimitive<T>> Primitives {get;set;}
}
Luigi answered 23/12, 2010 at 21:27 Comment(6)
T will be defined, but it is defined in each object that implements T. So in the example above it will be defined in Star and in Sun. So when I go to create the Generic List in another class, it does not know what T is. T could be a different type in Star and Sun. Would that still work?Spectroradiometer
@PB: No. There can be only a single T in List<T>.Luigi
T is defined in the interface, not the holding class. There's different.Argentina
@Argentina interfaces cannot define types. They can only define methods, properties, indexers and events. See learn.microsoft.com/en-us/dotnet/csharp/language-reference/…Luigi
You don’t understand the question. He doesn’t want define class just for holding the collection of T. All he wants just variable/field which’s defined as collection of IPrimitive<T>. There’s different.Argentina
@Argentina Yeah, it's different: it's not possible!Luigi
O
3

This is one of the most complicated elements of the c# language though it is incredibly important for building well defined components. As such, c# falls short. However it is definitely possible to make this work.

The trick is to have 3 parts:

  1. A non generic interface that contains all requirements of the interface.
  2. A generic abstract class that implements the non generic interface and performs the type conversions as necessary.
  3. A class that implements the generic abstract class with the appropriately typed results

For example:

public interface INonGenericInterface{
    void Execute(object input);
    object GetModel();
}

public abstract class IGenericInterfaceBase<T> : INonGenericInterface{
    void INonGenericInterface.Execute(object input){
        Execute((T) input);
    }

    object INonGenericInterface.GetModel(){
        return GetModel();
    }

    protected abstract void Execute(T input);
    protected abstract T GetModel();
}

public class ImplementingClass : IGenericInterfaceBase<ModelClass>{
    protected override void Execute(ModelClass input){ /*Do something with the input */ }  
    protected override ModelClass GetModel(){ return new ModelClass();}
}

//Extras for demo
public class ModelClass { }
public class ModelClass2 { }

public class ImplementingClass2 : IGenericInterfaceBase<ModelClass2>
{
    protected override void Execute(ModelClass2 input) { /*Do something with the input */ }
    protected override ModelClass2 GetModel() { return new ModelClass2(); }
}

var agi = new INonGenericInterface[] { new ImplementingClass(), new ImplementingClass2() };
agi[0].Execute(); var model = agi[0].GetModel();
agi[1].Execute(); var model2 = agi[1].GetModel();
//Check the types of the model and model2 objects to see that they are appropriately typed.

This structure is incredibly useful when coordinating classes w/ one another because you're able to indicate that an implementing class will make use of multiple classes and have type checking validate that each class follows established type expectations. In addition, you might consider using an actual class instead of object for the non-generic class so that you can execute functions on the result of the various non-generic calls. Using this same design you can have those classes be generic classes w/ their own implementations and thus create incredibly complex applications.

To OP: Please consider changing the accepted answer to this to raise awareness of the correct approach as all previously stated answers fall short for various reasons and have probably left readers with more questions. This should handle all future questions related to generic classes in a collection.

Obrien answered 28/11, 2021 at 19:46 Comment(3)
This solution solved the problem for me.Caracara
This is a very good answer!Posthaste
The drawback is that IGenericInterfaceBase is a class, as inheritance might not always be possible (e.g., if ImplementingClass already inherits another class).Bahr

© 2022 - 2024 — McMap. All rights reserved.