5

Can anyone explain me this issue?

the only way to get this working, is to use the virtual in the CorrectName and then override in the Derived one, instead of new keyword, but, WHY is this happening?

WHY if I cast by generic it gives me the Base value, and if I cast it directly it gives me Derived value? ((Output is below))

Thanks guys, as I've said you, I already got the "solution", but I want to UNDERSTAND

    class Base
    {
        public string Name { get; set; }
        public string CorrectName { get { return Name; } }
    }
    class Derived : Base
    {
        public new string CorrectName { get { return "NEW" + Name; } }
    }
    static void Main(string[] args)
    {
        List<Derived> container = new List<Derived>();

        var d = new Derived() { Name = "NameDerived2" };

        container.Add(d);

        Search<Derived>(container);
        Console.ReadLine();
    }

    static void Search<T>(List<T> list) where T : Base
    {
        foreach (var el in list)
        {
            Console.WriteLine("No Cast -->" + el.CorrectName);
            Console.WriteLine("Generic Cast -->" + (el as T).CorrectName);
            Console.WriteLine("Direct Cast -->" + (el as Derived).CorrectName);
        }
    }

OUTPUT:

No Cast -->NameDerived2

Generic Cast -->NameDerived2

Direct Cast -->NEWNameDerived2

TABLE OF TRUTH:

el is Derived == true
el.GetType().Equals(typeof(Derived)) == true
el.GetType().Equals(typeof(T)) == true
el.GetType().Equals(typeof(Base)) == false
typeof(T).Equals(typeof(Base)) == false
typeof(T).Equals(typeof(Derived)) == true
Luca Trazzi
  • 1,240
  • 1
  • 13
  • 30
  • Because T is `Base` not `Derived` so essentially you are casting to `Base` not `Derived` - when you cast to `Derived` the property `CorrectName` is resolved to be the one marked `new` because it hides the original property on `Base` - this isn't an issue or a bug - what do you expect it to do?? – Charleh Dec 21 '12 at 10:57
  • 1
    T Inherit Base, but I'm passing Derived as T, so I'm casting to Derived, not Base – Luca Trazzi Dec 21 '12 at 11:14
  • I thought this had to do with the fact that you're passing in a `List`, but changing to just take a single `T` has the same results. I think we need Eric Lippert or Jon Skeet to explain this. – juharr Dec 21 '12 at 11:27
  • No T is `Base` since you've constrained it to that. `Derived` inherits from `Base` so technically T *is* `Derived` in your generic class but the generic method doesn't know anything about `Derived` at design time so you could probably argue that it shoulnd't know about the `new` method on `Derived` or you open up to a world of hurt where `new` might break your expected behaviour (i.e. you don't mark the method virtual yet someone uses `new` and modifies your intended behaviour!). There are probably some special rules for this in the building of the generic class – Charleh Dec 21 '12 at 13:02

2 Answers2

4

Without the virtual keyword, the method in the base class is not overridden, but "hidden" by the new implementation. You're also enforcing this by using the new keyword in Derived. As you said in your generic method declaration, any T passed to the method must be of type Base, so every T is cast to Base.

What happens now is that when you don't use virtual, you lose polymorphism, that means, even though the object is actually of type Derived, but cast to Base, the Base implementation of CorrectName is called, not - as you'd expect - the new implementation in Derived.

This is only called when you explicitly cast your object to Derived.

This has also been discussed and described here: virtual keyword in c#

Another link that might help you understand the difference between virtual and non-virtual methods could be this: http://en.wikipedia.org/wiki/Virtual_method_table

Community
  • 1
  • 1
Thorsten Dittmar
  • 55,956
  • 8
  • 91
  • 139
  • "every `T` is cast to `Base`" - I'd expect `el as T` to perform a cast to `Derived` not `Base` (if indeed any cast at all, since it's already a `Derived`) - but `.CorrectName` to still call the `Base` version since at compile time that's the only method slot it knows about. – Rawling Dec 21 '12 at 11:06
  • 1
    "where T:Base" => this means to me that T MUST inherit from Base, but it's not necessarily Base. In this example, I'm passing Derived as T, so I Expect (T)el to be the same of (Derived)el – Luca Trazzi Dec 21 '12 at 11:13
  • Yes, `el` is actually a `Derived`, but thanks to `T:Base`, the method implicitly interprets `(T)el` as `(Base)el`. Actually, you're also passing a `List`, which is a `List` due to `T:Base`, so every `T` from the list is treated as a `Base`. – Thorsten Dittmar Dec 21 '12 at 11:31
  • 2
    @ThorstenDittmar Yet `typeof(T)` and `el.GetType()` both say `Derived`. I think it's more correct to say that the method treats the generic type based on the constraints rather than dynamically changing the behavior to the passed in type. – juharr Dec 21 '12 at 11:44
  • @ThorstenDittmar Also it doesn't really matter that it's `List`. Just passing in one `T` results in the same behavior. – juharr Dec 21 '12 at 11:46
  • Added table of truth in starting post – Luca Trazzi Dec 21 '12 at 11:52
  • @LucaTrazzi: And I bet that `el is Base` also returns `true`. – Thorsten Dittmar Dec 21 '12 at 12:06
  • @ThorstenDittmar Of course it does, but that doesn't mean `(T)el` casts to `Base`. – Rawling Dec 21 '12 at 12:17
  • @ThorstenDittmar Well, el is Derived which inherits Base, so, of course yes! – Luca Trazzi Dec 21 '12 at 12:18
  • @Rawling: No, it was just for the sake of completeness. – Thorsten Dittmar Dec 21 '12 at 13:20
0

Non-virtual methods are bound at compile-time, not run-time. At compile-time, all that can be guaranteed is that T is a Base, so the compiler binds the property accessor to the Base version of CorrectName and this will not change at run-time. You are calling the method with Derived as the type parameter T, but others could call it with another type inheriting from Base, or even Base itself.

Virtual methods, however, will inspect the actual runtime type and call the correct overridden method.

The "table of truth" you posted is irrelevant here, since all of those are evaluated at runtime. To prove that the compiler evaluates T as if it were Base, try the following:

T t = default(T);
object o = t; // works
Base b = t; // works
Derived d = t; // doesn't work
jam40jeff
  • 2,576
  • 16
  • 14
  • I like your post, but default(T) gives me null, as Derived is a reference type, I've tried by using new T(), and Derived d = t; worked – Luca Trazzi Dec 24 '12 at 07:55
  • Derived d = t; shouldn't even compile as long as that code block I posted was within the Search method. I only assigned default(T) to t (yes, I could have just assigned null as well, but I am in the habit of assigning default(T) if I'm just initializing a variable of a generic type) so the code was compilable (except for the last line). I didn't mean for it to have any meaning at runtime. – jam40jeff Dec 24 '12 at 23:46
  • I was able to access a computer and test this, and I do in fact get `error CS0029: Cannot implicitly convert type 'T' to 'YNAB.Derived'` when I try to compile that code. – jam40jeff Dec 25 '12 at 04:48