0

Two classes, one overrides Object.ToString() and one just hides it:

public class One
{
    public override string ToString() => "One";
}

public class Two : One
{
    public new string ToString() => "Two";
}

Then, I'm calling ToString in two different ways:

var item = new Two();
Console.WriteLine(item.ToString()); // first way
Print(item); // second way

Where Print is

public static void Print<T>(T item)
     => Console.WriteLine(item.ToString());

As result I get

Two
One

Which is not what I expected. My expected behavour would be that both calls will print the same text — Two. I thought that compiler will understand correctly what type is passed and, according to the type, correct method will be called, as T should be Two.
Acquiring type name proves that it's actually Two, but method resolution still works strangely.

Could you please explain what am I seeing? Is boxing involved somehow?

cassandrad
  • 3,412
  • 26
  • 50
  • 3
    You're not overriding, you're hiding by using `new`. – Alejandro Jun 25 '21 at 20:48
  • 1
    @Alejandro I fail to understand your input, could you please elaborate how that affects the behaviour? I hid the method with `new` intentionally. – cassandrad Jun 25 '21 at 20:51
  • 1
    The `as T`/`as Two` isn't helping with anything in either of those scenarios. You already had the type you were casting. – Joel Coehoorn Jun 25 '21 at 21:01
  • _"I hid the method with new intentionally"_ -- then you should not be surprised that, having hidden the base class method, you get a different result when calling the method via the derived class than you do when calling the method via the base class with the hidden method. Note that in the second example, the compile-time type of the object `item` is just `System.Object`; the method doesn't know anything about the method implementation in `Two` – Peter Duniho Jun 25 '21 at 21:04
  • @cassandrad Ok, question closed before finishing my answer, but in short, within `Print` it must be determined **at compile time** which `ToString` to call, and the only clue is that T is a class, hence it looks in `System.Object`, which is overriden in `One` and no more than that. `Two` doesn't overrides `ToString`. In the first case, the cast was explicitly to `Two`, then the compiler has full information for picking the `new`ed method instead the other. – Alejandro Jun 25 '21 at 21:07
  • @Alejandro now I got it, so in generics, at runtime `T` equals to `Object` due to strange compiler behaviour. Thanks for the explanation. I've looked at many docs and it's not mentioned anywhere, sadly. – cassandrad Jun 25 '21 at 21:09
  • 1
    _"T equals to Object due to strange compiler behaviour."_ -- it's not strange at all. The whole point of generics in C# is that the _compiler_ generates code based on _what it knows_ about the generic type parameter(s) _at compile time_. If you'd constrained `T` to be `Two`, it'd call the method you expected. But you didn't. So it doesn't. This is entirely predictable and comprehensible under the C# generic type rules. – Peter Duniho Jun 25 '21 at 21:11
  • And the result would be the same with `where T : One`. – GSerg Jun 25 '21 at 21:11
  • 1
    Generics are not specialized. C++ templates are specialized, for each actual value of the template parameters, the compiler processes the template function and can pick different function overloads, perform different operations. .NET generics do not do this. They are processed *once* using only the constraints on the generic arguments. – Ben Voigt Jun 25 '21 at 21:46
  • @BenVoigt if there is no constraint, how will it work? – cassandrad Jun 25 '21 at 21:47
  • 2
    @cassandrad: Without a constraint, you can only use methods that exist on every single object. `Object.ToString()` is one of those. There are very few types in .NET that aren't derived from `System.Object` and the ones that aren't also can't be generic type arguments. So in some sense "no constraint" is impossible because every type argument has an implicit "derived from `System.Object`" constraint, even if you didn't write an constraints. – Ben Voigt Jun 25 '21 at 21:54
  • 1
    And that's why `T` is `Two` (Peter's comment claiming otherwise is 100% wrong) and it also doesn't matter, because the actual runtime value(s) of `T` are not used when the generic method is compiled. – Ben Voigt Jun 25 '21 at 21:56
  • @BenVoigt thanks for the comment! That's actually what I wasn't understanding. Sad that the question is closed and that can't be posted as an answer. It actually clarifies the situation and results. – cassandrad Jun 25 '21 at 21:59
  • @BenVoigt: _"Peter's comment claiming otherwise is 100% wrong"_ -- hardly. At the time that the method is compiled, `T` is treated as `System.Object`. The compiler has no way to know it might be `Two` at some point. It certainly is _not_ `T` at that point. Your claim otherwise is 100% wrong. – Peter Duniho Jun 26 '21 at 00:10
  • @PeterDuniho: Baloney. `T` is known at compile time to be "any and all types derived from `System.Object`". If `T` *were **equal** to* `System.Object` (at the time of compilation) as you claim then you could write `T x = new object();` but you cannot. – Ben Voigt Jun 26 '21 at 22:11
  • @Ben: ah, yes...I see how when you put words into my mouth, you come up with a nice straw man you can easily knock over. Well, whatever...have fun with that. – Peter Duniho Jun 26 '21 at 22:17
  • @PeterDuniho: [Your exact words were "the compile-time type of the object `item` is just `System.Object`"](https://stackoverflow.com/questions/68136879/new-modifier-works-unexpectedly-inside-a-generic-method-call?noredirect=1#comment120426773_68136879) (wrong as I've explained) and [then you agreed with OP that "T equals to Object" and only took issue with his assessment of "strange compiler behaviour"](https://stackoverflow.com/questions/68136879/new-modifier-works-unexpectedly-inside-a-generic-method-call?noredirect=1#comment120426874_68136879). What you actually said is not a straw man. – Ben Voigt Jun 26 '21 at 22:28

0 Answers0