Learn C#: How Do Virtual Methods Work?

2024 ж. 22 Мам.
5 015 Рет қаралды

Become a sponsor to access source code ► / zoranhorvat
Join Discord server with topics on C# ► codinghelmet.com/go/discord
Enroll course Beginning Object-Oriented Programming with C# ► codinghelmet.com/go/beginning...
Subscribe ► / @zoran-horvat
Do you know how C# knows which implementation of a virtual method to execute when you make a call to it? First, it is not C# - it is the .NET Runtime who decides. But that is the lesser problem. The question remains: How does .NET Runtime know which implementation to execute when you make a call to a virtual method on an object?
We ask the same question about .NET Reflection, too. How does the GetType() method know the right type object to return when we call it on a reference to its base type? Such a confusion!
This video dives into the structure of every instance of every reference type in .NET. You will learn about the type descriptor and the table of virtual methods - two fundamental traits of every object you instantiate. By understanding how method resolution works during run time, you will know everything about virtual methods in .NET and C# and any other object-oriented language, all the same.
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
⚡️RIGHT NOTICE:
The Copyright Laws of the United States recognize a “fair use” of copyrighted content. Section 107 of the U.S. Copyright Act states: “Notwithstanding the provisions of sections 106 and 106A, the fair use of a copyrighted work, including such use by reproduction in copies or phono records or by any other means specified by that section, for purposes such as criticism, comment, news reporting, teaching (including multiple copies for classroom use), scholarship, or research, is not an infringement of copyright." This video and our youtube channel, in general, may contain certain copyrighted works that were not specifically authorized to be used by the copyright holder(s), but which we believe in good faith are protected by federal law and the Fair use doctrine for one or more of the reasons noted above.
#csharp #dotnet #objectorientedprogramming

Пікірлер
  • Maybe it's also good to mention here that the Microsoft advice is to make your classes sealed when you can, especially when they are also internal, which is also recommended by default. They do it internally in most libraries as well. Important point with this dynamic dispatch is that it all comes with a performance overhead, but when the compiler or the runtime sees a sealed class, it know that it no longer needs to search for further derived types and that it can stop searching further. It's also a better choice as nowadays composition is preferred over inheritance.

    @jongeduard@jongeduard2 ай бұрын
    • That is a good advice.

      @zoran-horvat@zoran-horvat2 ай бұрын
    • how is that relevant to virtual methods?

      @ethanr0x@ethanr0x2 ай бұрын
  • Just discovered your channel yesterday while looking up information on overload selection. Fitting with this and your earlier videos on how the runtime selects virtual functions could be overload selection. A great example of why it's weird: if a derived class Foo has two methods name Bar, one taking a double, and another that is an override which takes an int, and you call foo.Bar(10) the method that takes a double is chosen, which is not what you might think at first.

    @codemonkey6173@codemonkey61732 ай бұрын
    • That is because C# is component-based language. Most programmers don't have a clue what that means, but this anomaly is the result: the compiler looks into the current type first and only if it cannot resolve the call will it look into another component, such as the base type.

      @zoran-horvat@zoran-horvat2 ай бұрын
    • @@zoran-horvat yup! Even for me this one was surprising. I'd argue the wrong choice as well, due to the potential for error. At the least it should cause a compiler warning. Even chatGPT, which is pretty good at common language oddities, was insistent it should be the int overload 😂 Maybe I'll write an analyzer for these.

      @codemonkey6173@codemonkey61732 ай бұрын
  • Spoiler: Interface methods implemented in a class (not struct!) are implicitly "technically" virtual, and there is a v-table, even without the virtual keyword. You can see it in the IL.

    @cdoubleplusgood@cdoubleplusgood2 ай бұрын
    • Precisely, but with a caveat that makes them confusing. For instance, hiding a method with the new keyword doesn't override the method in the interface, but if you add the interface to the derived class as well (which is syntactically a nonsense), then it does!

      @zoran-horvat@zoran-horvat2 ай бұрын
    • @@zoran-horvat Well, that sounds surprising. Wonder whether there is a reason behind this or if it is just a quirk of the compiler.

      @cdoubleplusgood@cdoubleplusgood2 ай бұрын
    • @@cdoubleplusgood I am viewing it the way pointer casting and multiple inheritance work in the C++ compiler. If there is an interface I and base class B : I and derived D : B, then I imagine the call to i.f() defined on I makes the compiler look for I in B, no matter whether the object is B or D, because there is no I in D. I suppose this thought sounds confusing, but to me it explains why the runtime cannot see the new method in D unless D is defined as D : B, I. In the latter case, there would also be I in D, and the runtime would resolve to the vtable in D this time. Anyway, this situation is confusing because the idea that one could hide a method with new and in that way override it in the interface but not in the class is super confusing itself. It can hardly be made any cleaner by the compiler.

      @zoran-horvat@zoran-horvat2 ай бұрын
    • @@cdoubleplusgood One detail slipped my attention. The compiler must not turn an interface method into a virtual one in such a way that it truly behaves like virtual, so for example that new keyword lets us override it in the derived class. Since the base class declared it as non-virtual, it must remain non-virtual no matter how the code is compiled. The compiler adding it to the vtable is a smart move, as that lets the runtime use the method resolution it already has at its disposal. But the method resolution must be preceded by pointer casting first, so that the runtime accesses the right vtable.

      @zoran-horvat@zoran-horvat2 ай бұрын
    • And then we make this even more confusing with default implementations in the interface definition! How do you make the default implementation of an interface method callable via the implementing type? By casting itself to the interface and calling the default implementation explicitly!

      @Sindrijo@Sindrijo2 ай бұрын
  • Hi and thank you again for this informative video. If I could suggest a specific video topic, I personally and other members here would surely be very interested in seeing a video from you that is about encapsulation regarding collections, as in what kind of interface would you want to expose for methods (per example "EntityFramework"), based on the intention of the method (do we want to modify the collection or not?) I have seen you also frequently use the "IEnumerable" interface, but it generally represents a contract for implementing a streaming-like behaviour collection, unlike just a collection that you want to iterate over in a non-streaming manner. Typically we would want to expose contracts for types that provide the most functionality that we would want to access, and not the least. (If I only want to read the list data, I don't want it to be mutable, right?) Thank you! 😄

    @zhenglaowang8489@zhenglaowang84892 ай бұрын
  • What I find interesting is that the metadata method token for the base class is used every time there is a virtual method invokation. Does that mean that this token (6xxxx1) e.g. is used as a key for the method table dictionary for the derived types as well?

    @ethanr0x@ethanr0x2 ай бұрын
    • If that is the class identifier, then not - it would always be the value taken from the current object, i.e. the one associated with the drives type. In the .NET implementation, objects carry a 16-bit header field that leads to the table with metadata, but that is the platform implementation detail which should not affect our conclusions.

      @zoran-horvat@zoran-horvat2 ай бұрын
  • Was it a cliffhanger in the end of the video? =)) Thank you Zoran for the useful materials. But when you override base class methods don't you violate LSP SOLID principle? I would appreciate if you share your thoughts on this topic

    @seriyezh@seriyezh2 ай бұрын
    • That depends on what the overriding code does. It must not "break" the contract defined by the base implementation (or interface definition). That means, if you pass a derived object were you could also pass a base object, the program must not break. For example, if the method has a parameter, and that was allowed to be null in the base implementation, null must also be valid in the derived implementation.

      @cdoubleplusgood@cdoubleplusgood2 ай бұрын
    • LSP is the most delicate of all SOLID principles, and the one causing great confusion. You can help yourself if you see it this way: Assign a set of attributes, propositions, call them what you like, to a method. Those must always be true, no matter how you implement the method. That gives you guidance in encapsulation . Then comes inheritance and a potential override, changing the implementation. What LSP states is that the override will satisfy the same attributes, too. Therefore, some concrete overrides will break LSP and some will not, but you cannot tell in advance. LSP is guidance to the inheritors, giving the list of rules for each individual method their implementation must satisfy to be correct.

      @zoran-horvat@zoran-horvat2 ай бұрын
  • goat

    @poloolo69@poloolo692 ай бұрын
  • I'm disappointed, you didn't call the base method from the overriden one.😅

    @nickbarton3191@nickbarton31912 ай бұрын
    • Gotcha!

      @zoran-horvat@zoran-horvat2 ай бұрын
KZhead