1

I have the following interface

public interface IInterface
{
    int GetId();
}

ClassA is a generic class but T should implement IInterface

public class ClassA<T> where T : IInterface {

}

ClassB implements IInterface

public class ClassB : IInterface {
    public int GetId() {
        return 1;
    }
}

ClassC is where my problem is, namely adding items to the list.

public class ClassC {
    List<ClassA<IInterface>> list = new List<ClassA<IInterface>>();    

    public void Add<T>(ClassA<T> item) where T : IInterface {
        list.Add(item);
    }
}

The code above gives the following error which makes sense

cannot convert from 'Program.ClassA<T>' to'Program.ClassA<Program.IInterface>'

The core of the problem is in the Add() method where my goal is to be able to add any type of ClassA. I am not sure how to design this in order to achieve the following

ClassC classC = new ClassC();
ClassA<ClassB> classA = new ClassA<ClassB>();
classC.Add(classA);

Any ideas?

poke
  • 369,085
  • 72
  • 557
  • 602
user3170354
  • 391
  • 1
  • 4
  • 11

2 Answers2

3

What about using Covariant Generic Type Argument.

public interface IInterface
{
    int GetId();
}

// Contra-variant and covariant generic type argument can be used only in interfaces and delegates
public interface IClassA<out T> where T : IInterface 
{
}

public class ClassA<T> : IClassA<IInterface> where T : IInterface { }

public class ClassB : IInterface
{
    public int GetId()
    {
        return 1;
    }
}

public class ClassC
{
    List<IClassA<IInterface>> list = new List<IClassA<IInterface>>();

    public void Add(IClassA<IInterface> item)
    {
        list.Add(item);
    }
}
public class Test
{
    public static void Run()
    {
        ClassC classC = new ClassC();
        ClassA<ClassB> classA = new ClassA<ClassB>();
        classC.Add(classA);
    }
}
  • Glad it helped :) – Darkhan Zholmukhanov Apr 17 '19 at 09:30
  • 3
    It's actually [Covariance](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance) not contravariance. (I often have to check to make sure I've got them the right way around too) – Damien_The_Unbeliever Apr 17 '19 at 09:32
  • 1
    Those names are confusing – Darkhan Zholmukhanov Apr 17 '19 at 09:44
  • 1
    Eric Lippert did a good [series on it back in 2007](https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/page/3/). Part one describe why one is `co-` and the other `contra-` at a very high level but it still doesn't make it easy (for me) to see it when I'm working with real C# code. If you haven't read the series before (or want a refresh on it) I'd recommend it. – Damien_The_Unbeliever Apr 17 '19 at 10:16
  • Thank you. I will check that out. Also Jeffrey Richter explains this concept in his book "CLR via C#". I may have to reread that part because it is still not very clear to me. – Darkhan Zholmukhanov Apr 17 '19 at 11:28
0

Depending on the specifics of your design, you may be able to split ClassA in two:

public abstract class ClassA {
  //Stuff that only works in terms of IInterface,
  //maybe with some abstract methods, required constructors, etc
}
public class ClassA<T> : ClassA where T : IInterface {
  //Stuff specific to T
}

You then store ClassAs in your list and cast them to their known concrete type as/when you know what that is and need to work in those terms (e.g. to be able to call ClassA<T> specific functionality)

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448