1

I asked a question earlier with regards to using generic vs base class in a method taking in a List<T>. It was quickly pointed out that you can't pass a List<Derived> into a method expecting a List<Base>.

Further exploration (along with helpful links such as this one) show that while you can't pass List<Derived> to List<Base>, you can pass List<Derived> to IEnumerable<Base> (due to contravariance/covariance in .NET 4).

So like in my previous question, I present the same one here: Since there doesn't appear to be a difference in using these two options, which would be better? Is it strictly a style-based thing, or are there implications to using one over the other?

Option A: void DoWork<TBase>(IEnumerable<TBase> objs) where TBase : Base;

Option B: void DoWork(IEnumerable<Base> objs);

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Doctor Blue
  • 3,769
  • 6
  • 40
  • 63
  • 2
    Option A would enable to write something like `new TBase()` within the method ... just an advantage that comes to mind immediately. – ViRuSTriNiTy Jan 29 '18 at 14:14
  • @HimBromBeere You could add diffferent types to the first collection, too, using `Base` as `TBase`. – René Vogt Jan 29 '18 at 14:16
  • 2
    Indeed, unlike using `List`, Using `IEnumerable` will allow you to send to this method any instance that implements `IEnumerable` where `X` is derived (or implements) `T`. So to the question of what is better - Well, In this case I don't think there is much of a difference between these two implementations. The Generic one might generate more compiled code, but I hardly think it's going to matter. – Zohar Peled Jan 29 '18 at 14:20
  • 1
    @ViRuSTriNiTy No, it wouldn't - try it. You'll get an error: `Cannot create an instance of the variable type 'TBase' because it does not have the new() constraint`. If you change the declaration to `where TBase : Base, new()` you could use `new` - but of course then you would only be able to use it with types that had a default constructor. – Matthew Watson Jan 29 '18 at 14:29
  • @MatthewWatson This is correct, simply forgot to mention it. – ViRuSTriNiTy Jan 29 '18 at 14:31

2 Answers2

1

Option A

void DoWork<TBase>(IEnumerable<TBase> objs) where TBase : Base;

Elements in the IEnumerable are constrained according to TBase. If TBase inherits from Base then the IEnumerable cannot contain Base.

Option B

void DoWork(IEnumerable<Base> objs);

The IEnumerable can contain Base or anything that inherits from it.

In either case, unless there's some ugly type-checking, each method can only interact with elements in the IEnumerable according to the definition of Base because that's all it "knows" about them.

The difference would be more meaningful if the method returned Base or TBase. Then the caller would be able to pass in TBase and get a result that is cast as TBase. If the type matters more to the caller than it does to this method then it wouldn't want to pass in an argument that inherits from TBase and get its result cast as Base.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
1

Option A jits slightly more, because DoWork(new TDerived[0]) is a different method call than DoWork(new TBase[0]). There'll be a lot of shared code in the jitter's output (because TBase and TDerived must be reference types to actually be different and have one derived from the other), but it is a bit extra.

Theoretically if DoWork calls a virtual method and TDerived was sealed, then it's possible for the jitter to take advantage of that which would give a slight benefit, but I'm pretty sure that won't happen due to code-sharing. If it did happen the difference would be minute.

So, in all option B wins in not jitting more versions of the generic method and option A has no compensatory advantage.

However, option A could be more useful if it was:

TBase DoWorkAndReturnSomething(IEnumerable<TBase> objs) where TBase : Base

Where you avoid having to cast the result. This would only really be an advantage if you actually were casting the result. If you went on to use it as a Base or pass the result to a method that took a Base then you've won nothing to compensate for your extra jitting and extra conceptional complexity.

So there are pros and cons to each approach. The non-generic is likely to be the better for a wider range of cases, but there are times when the generic form with constraints does have an edge.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251