andrewducker: (Default)
[personal profile] andrewducker
According to the C# specification:

In a virtual method invocation, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke. In a non-virtual method invocation, the compile-time type of the instance is the determining factor. In precise terms, when a method named N is invoked with an argument list A on an instance with a compile-time type C and a run-time type R (where R is either C or a class derived from C), the invocation is processed as follows:

  • First, overload resolution is applied to C, N, and A, to select a specific method M from the set of methods declared in and inherited by C. This is described in Section 7.5.5.1.
  • Then, if M is a non-virtual method, M is invoked.
  • Otherwise, M is a virtual method, and the most derived implementation of M with respect to R is invoked.

all of which boils down to - non-virtual methods take priority over virtual ones, even if the virtual ones are a better fit.

Which means that if you have two methods on a class:
virtual void DoSomething(string myString)
and
void DoSomething(object myObject)

and you call:
myClass.DoSomething("Hello World");

then it calls the object version rather than the string version!

OhForGoodnessSake.

Date: 2010-04-05 03:47 pm (UTC)
From: [identity profile] jarkman.livejournal.com
"Any sufficiently advanced technology is indistinguishable from a pain in the arse", as a wise man once said.

Date: 2010-04-05 04:01 pm (UTC)
From: [identity profile] call-waiting.livejournal.com
Hm. Interesting. Have you tried it? 'cause that's not quite the way I read that.

Paraphrasing:

1. method M is selected according to best fit, regardless to virtual/concrete. (maybe the linked article describes something about not selecting virtual methods? I didn't read it, I just searched for 'virtual' and didn't find any mention)
2. if M was a concrete method, call that method
3. if M was a virtual method, use the normal virtual method despatch to find the right one for the runtime type R.

In your example, M would be selected as the best-fit method, the virtual method with the String parameter, and then dispatched as a virtual method. Which is probably exactly what you'd expect.

Date: 2010-04-05 06:50 pm (UTC)
From: [identity profile] call-waiting.livejournal.com
It makes sense to me, as I consider virtual and concrete method calls to be two distinct things (compile-time resolution and dynamic resolution). So if you exclude overridden methods, you should be guaranteed to resolve to the base-class virtual method (presumably it's a compile-time error to 'override' something that's not already declared as 'virtual' in the base class?), the compiler will create a virtual method call, which will resolve dynamically to the override method you'd expect to call.

So that makes sense to me. I'm still failing to see why your String-parametered method wouldn't be called! :)

Date: 2010-04-05 07:41 pm (UTC)
From: [identity profile] call-waiting.livejournal.com
Ah, I see, yes. The sensible thing to do (well, at least the obvious one!) would be to accept the overriding method as an alias to the virtual one. Tsk tsk.

Date: 2010-04-05 04:36 pm (UTC)
simont: A picture of me in 2016 (Default)
From: [personal profile] simont
I don't like to be the annoying person who says "don't do that, then" all the time, but ... why do you want to have a virtual and non-virtual method sharing the same name? It sounds like a recipe for confusion to me, whichever one ends up getting selected. I'm faintly surprised it's not simply illegal – if I'd been designing an OO language, it probably would have been.

(But then, if I were designing an OO language, something very weird would have to have happened to the universe.)

Date: 2010-04-05 04:59 pm (UTC)
simont: A picture of me in 2016 (Default)
From: [personal profile] simont
That seems like a very weird interface to me. List indices and bus route numbers are semantically completely different things, whose only thing in common is that they happen to fit into the same type. Surely it would make much more sense to call the bus list's new methods GetItemByRoute and GetItemByDestination (abbreviated if you prefer, which I probably would), and still be able to access the by-list-index method from the base class? That way, you could still iterate over all routes one by one in the obvious way: for (i=0; (entry = List.GetItem(i)) != nil; i++).

I would have thought that a more semantically sound reason to override GetItem would be if you were implementing List via a different data structure (perhaps one with different efficiency guarantees – tree versus array versus linked list), so that the index passed in was semantically similar.

But even given that, why is it important to have the second method be non-virtual? (I suppose a problem here is that I've never actually understood why it might be semantically desirable for a method not to be virtual, unless it's a completely private subroutine used in the implementation of the class's public interface. For a public method, the only virtue I can see of non-virtual-ness is performance, and in an ideal world that really ought to be a job for the compiler to work out automatically, by observing that no polymorphic dispatch ever needs to happen on that method and hence optimising out the indirection at compile time.)

It seems baffling to me that you'd be able to have both virtual and non-virtual methods and then make it impossible to call the virtual ones.

No argument there; it's just that we seem to disagree on which half of that sentence to fix :-)

Date: 2010-04-05 05:37 pm (UTC)
simont: A picture of me in 2016 (Default)
From: [personal profile] simont
Mmm. So where you say "non-virtual" there, you really mean something like "not permitted to override this method at all"? I was thinking of "non-virtual" in the sense of the specific behaviour if you do override it; that's the thing I see little use for. Certainly I can see the use of methods marking somehow that they're not set up to be conveniently overridable, and Hejlsberg's arguments there make a lot of sense to me on that point.

But that makes your GetItem example rather more strange. In that specific example, it's hard to see that there would be a need for interesting guarantees about calling order: a collection class surely has to be able to serve any GetItem request of any type, at any time the class is valid, and that's all there is to it. So I still don't see why all those GetItems can't be virtual just as safely as the original one.

A more complex class which had a non-trivial user-visible model of its internal state so that there were interesting constraints on the usage of its API – such as, indeed, something in an OS API – would have a better claim to Hejlsberg's caveats. But in that situation, I think I stand by my original claim: if you're going to have two methods distinguished only by their type signatures (which may not even be obvious at the point of call, if you have to look elsewhere still to check the type of some variable passed in as a parameter), but whose semantics are so fundamentally different that one is virtual and designed to be overridden while the other is absolutely static, that is a recipe for confusion.

Date: 2010-04-05 07:36 pm (UTC)
simont: A picture of me in 2016 (Default)
From: [personal profile] simont
Sorry, that was careless wording. I didn't mean "static" in the technical sense; I just meant it stays fixed and isn't ever overridden to do something different.

(Sometimes computing's tendency to take over absolutely every word in the core English vocabulary and turn it into a precise technical term makes it difficult to find any words left to talk about things without accidentally meaning something different...)

Date: 2010-04-06 06:20 am (UTC)
From: [identity profile] call-waiting.livejournal.com
Why on earth would you want:
GetItemByDestination(Destination destination);
and
GetItemByRoute(Route route);

when you could have
GetItem(Destination destination);
and
GetItem(Route route);


While either may be considered as equally readable and it's just as easy to understand the intention of the program in either case, the unambiguous method names make it easier to reason (either informally or formally) about the behaviour of the program. You can know exactly which method will be called without resorting to examining the types of the operands (and applying complex rules such as those we've talked about).

You can find all call-sites for an unambiguously named method easily with very simple tools such as grep and cscope, so it's much easier to refactor and redesign existing code.

To borrow a phrase from the ui people, the first set of method names are more WYSIWYG :)

These are, essentially, the reasons I'm a C luddite.

April 2026

S M T W T F S
    1 2 34
567 8 9 10 11
12131415161718
19202122232425
2627282930  

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Apr. 12th, 2026 03:10 pm
Powered by Dreamwidth Studios