Second approach is not different in any sense to first approach.
Deriving class B from A in no way will change the IL code generated for class A. B is simply inheriting those methods.
If you look at the IL code for class A, you can see that it compiled to call generic version instead of non-generic version -
.method public hidebysig instance void DoStuff(!T 'value') cil managed
{
.maxstack 8
L_0000: nop
L_0001: ldarg.0
L_0002: ldarg.1
L_0003: call instance void
ConsoleApplication1.A`1<!T>::InternalDoStuff(!0) <-- Generic version
L_0008: nop
L_0009: ret
}
From Jon Skeet's article here -
Just as a reminder, overloading is what happens when you have two methods with the same name but different signatures. At compile time, the compiler works out which one it's going to call, based on the compile time types of the arguments and the target of the method call. (I'm assuming you're not using dynamic here, which complicates things somewhat.)
As he mentioned using dynamic defer resolution till runtime. This piece of code will call non-generic version method for both of your approaches -
public void DoStuff(T value)
{
dynamic dynamicValue = value;
InternalDoStuff(dynamicValue);
}
Refer to the answers here by Jon Skeet and Eric Lippert describing in detail.