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#.