A new annoyance with C#
Apr. 5th, 2010 04:27 pmAccording to the C# specification:
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.
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
Nis invoked with an argument listAon an instance with a compile-time typeCand a run-time typeR(whereRis eitherCor a class derived fromC), the invocation is processed as follows:
- First, overload resolution is applied to
C,N, andA, to select a specific methodMfrom the set of methods declared in and inherited byC. This is described in Section 7.5.5.1.- Then, if
Mis a non-virtual method,Mis invoked.- Otherwise,
Mis a virtual method, and the most derived implementation ofMwith respect toRis 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.
no subject
Date: 2010-04-05 03:47 pm (UTC)no subject
Date: 2010-04-05 03:57 pm (UTC)And I've just realised that there is _no_ way of calling the string version of that method. That's just crazy!
no subject
Date: 2010-04-05 04:01 pm (UTC)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.
no subject
Date: 2010-04-05 04:24 pm (UTC)Aha!
http://msdn.microsoft.com/en-us/library/aa691331%28v=VS.71%29.aspx
First, the set of all accessible members named N declared in T and the base types of T is constructed. Declarations that include an override modifier are excluded from the set.
I know that only methods in the current class are called unless none match (at which point the base class is inspected for a match). It looks like overrides are removed so that only original virtual method is eligible for matching.
This seems completely counter-intuitive to me, but I can see their thought process. Personally, I'd make all methods on the current class (whether they be on that class, on the base class, or something else) equally likely to work.
no subject
Date: 2010-04-05 04:36 pm (UTC)(But then, if I were designing an OO language, something very weird would have to have happened to the universe.)
no subject
Date: 2010-04-05 04:41 pm (UTC)If you have a generic collections class, which allows you to get things by number, then the default action is to return the first one if you pass in 0, the second if you pass in 1, etc. If, however, you then pass in things that have numbers assigned to them, then the action should be to return the one with the number you pass in.
And if the collection of that type has other methods of retrieving things, then you'd want to add those in as well.
So, your base class is:
List
{
virtual T GetItem(int positionInList);
}
and your subclass is:
List
{
override Bus GetItem(int routeNumber);
Bus GetItem(string destination);
}
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 subject
Date: 2010-04-05 04:59 pm (UTC)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 :-)
no subject
Date: 2010-04-05 05:08 pm (UTC)GetItemByDestination(Destination destination);
and
GetItemByRoute(Route route);
when you could have
GetItem(Destination destination);
and
GetItem(Route route);
You're creating longer method names that duplicate information for no good reason.
As for virtual methods:
When we make something virtual in a platform, we're making an awful lot of promises about how it evolves in the future. For a non-virtual method, we promise that when you call this method, x and y will happen. When we publish a virtual method in an API, we not only promise that when you call this method, x and y will happen. We also promise that when you override this method, we will call it in this particular sequence with regard to these other ones and the state will be in this and that invariant.
Every time you say virtual in an API, you are creating a call back hook. As an OS or API framework designer, you've got to be real careful about that. You don't want users overriding and hooking at any arbitrary point in an API, because you cannot necessarily make those promises. And people may not fully understand the promises they are making when they make something virtual.
from and interview with Anders Hejlsberg (lead C# architect)
no subject
Date: 2010-04-05 05:37 pm (UTC)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.
no subject
Date: 2010-04-05 06:50 pm (UTC)So that makes sense to me. I'm still failing to see why your String-parametered method wouldn't be called! :)
no subject
Date: 2010-04-05 07:33 pm (UTC)C# starts at the subclass and looks for matching methods. It's only if it fails to find one that it goes back to the subclass to check for matching ones. So in this case it will go to the object version on the subclass. If that one didn't exist it would then look on the base class and find the virtal method - and _then_ when the call was actually made at runtime it would look for an override to call and find the overridden version.
no subject
Date: 2010-04-05 07:34 pm (UTC)no subject
Date: 2010-04-05 07:36 pm (UTC)(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...)
no subject
Date: 2010-04-05 07:37 pm (UTC)no subject
Date: 2010-04-05 07:41 pm (UTC)no subject
Date: 2010-04-05 07:43 pm (UTC)Now I actually understand what's going on though, it wouldn't matter whether the other method was marked virtual, because it would still find the one on that class before it found the one on the base class. It just took me a while to work out that overridden methods are "found" as if they were the original method on the base class, not as if they were defined newly on the subclass.
no subject
Date: 2010-04-05 07:55 pm (UTC)In fact, if I do this:
((BaseClass)c).DoStuff("Hello");
then it calls the overridden version of the String method. Because it compiles a callvirt to the BaseClass.DoStuff(string myString) method, and then at runtime it routes the call to the override.
So:
Compile time: check each class down the hierarchy for a method that can fit the parameters you call with. Stop when you find one. There might be a better fit further down the hierarchy, but if they wanted that, they'd have been more explicit.
Run time: Call that method, picking up any overrides to it.
Now I actually understand what they're doing, I can use it properly. But I still don't agree with the way they do the compile time checking.
no subject
Date: 2010-04-06 06:20 am (UTC)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.
no subject
Date: 2010-04-06 07:16 am (UTC)