3

I have a basic structure of generic's classes

public class Parent<T> where T : Parent<T>
{
   Action<T> Notify;
}

public class Child : Parent<Child>
{
}

And I want to have a list so that Child objects can be put there

List<Parent> parents = new List<Parent>();

In java I just can write List<? extends Parent> and all Parent's subclasses easily can be added to the list. Is there any alternative of this in C#?

lancaster
  • 134
  • 9
  • No, it is not. I've got compilation error `Generic.List requires '1' type arguments` (it's localized but something like this) – lancaster Feb 21 '20 at 06:25
  • Because of the type argument, you have to declare it `List>`. – madreflection Feb 21 '20 at 06:27
  • 1
    That type constraint is smelly. Are you doing it because it looks close to something you do in Java, or are you doing it because you can't do what you need in C# without it? – madreflection Feb 21 '20 at 06:29
  • 1
    Why is your base class generic? – ProgrammingLlama Feb 21 '20 at 06:32
  • @madreflection ok, if I have ChildOne and ChildTwo classes, how I can put them into the single list? – lancaster Feb 21 '20 at 06:32
  • 4
    Drive from the same class. The type constraint makes that impossible because the base class ends up being different. C# has reified generics, not type erasure. – madreflection Feb 21 '20 at 06:33
  • I'm not a Java programmer, so forgive me if this is perfectly fine in Java, but how would you use such a list? If you had a list of an open generic `Parent<>`, how would you use `Notify` if the list contained instances of `ChildA` and `ChildB`? – ProgrammingLlama Feb 21 '20 at 06:36
  • Re your edit: the constraint makes sense now, but it still locks you out of directly what you're trying to do. Instead declare two classes, `Parent` and `Parent : Parent`, then declare the list as `List`. Move as much as you can to `Parent` and only put in `Parent` what requires knowledge of `T`. – madreflection Feb 21 '20 at 06:37
  • The "duplicates" answer the question of generic constraints, but not the more specific problem of "how to get a generic type argument to accept _any_ `List>`". – Luaan Feb 21 '20 at 07:05

2 Answers2

2

You can't do the same thing as Java, because in Java, generics use type erasure and break variance. Essentially, your Java code turns everything into List<object>, and hopes for the best. The only reason why List<?> is better than List<Object> is that not everything is an Object in Java - in C#, you can put an integer inside a List<object> just fine. Mind, a List<int> will perform much better than List<object>, if you can afford it - that's one of the big reasons why generics were originally added to C#.

C# is a bit stricter than that. You can't do anything like new List<Parent<T>>() that would allow any kind of Parent<T>. If you had more limited requirements, you could use a variant interface instead, but that wouldn't work with List<T> for obvious reasons.

Your only real option is to make the base class non-generic. The user of your list can't know anything about the T in advance anyway, so any part of the Parent interface that returns or takes T wouldn't be useful without casting anyway (Java does the casting for you, but it's still casting - neither Java's nor C#'s generics are powerful enough for what you're trying to do).

public abstract class Parent
{
  // The common methods
  public abstract int Id { get; }
}

public abstract class Parent<TChild> : Parent, IEnumerable<TChild>
{
  // The methods that are TChild-specific - if you don't need any of those, just drop
  // this class, the non-generic one will work fine
  private List<TChild> children;
  public void Add(TChild child) => ...
  public TChild this[int index] => ...
}

public class Child : Parent<TChild>
{
  ...
}

Now to get a list of all possible children, you can use

var list = new List<Parent>();

And when you need to get e.g. all the Child items, you can do

var children = list.OfType<Child>();

Just for completeness sake, you can get similar behavior to Java's with C#'s dynamic. But I'm not even going to show any sample of that - dynamic is a useful tool, but mainly for more dynamic typing problems. It's overkill for something as simple as this, and trades compile-time issues for run-time issues.

In general, if you ever use Parent<T> directly, it should be in a generic method - e.g. an extension method that has some common functionality for all Parent<T>s. You can't instantiate a generic type that doesn't have all the type arguments known at the time in C#.

Luaan
  • 62,244
  • 7
  • 97
  • 116
2

Declaration List<Parent> parent; does not compile, since it requires type argument.

And when you say, public class Child : Parent<Child> it inherits Parent<Child> and not Parent<T>

So List<Parent<Child>> list; will only accept objects of Child class, and not of any other subclass of Parent.

Still you can achieve what you need with help of an interface as below: working fiddle here

public class Program
{
    public static void Main()
    {
        List<Parent<IParent>> parentList = new List<Parent<IParent>>();

        parentList.Add(new Child1());
        parentList.Add(new Child2());       
    }
}
public class Parent<T> 
{ }
public interface IParent
{ }

public class Child1 : Parent<IParent>, IParent
{ }

public class Child2 : Parent<IParent>, IParent
{ }
as-if-i-code
  • 2,103
  • 1
  • 10
  • 19