In our product we have a UI library that is referenced by many modules. The UI library and the modules are distributed as NuGet packages. Modules reference the UI library. A base app references the modules and also references a version of the UI library. Not every module is using the latest release of the UI library, so the base app can actually have a higher UI library version than the one used in modules.
In the UI library we provide a LibraryBasePage
that inherits from a Xamarin.Forms ContentPage
. A bugfix requires to override the OnAppearing inside the LibraryBasePage.
## Before the change:
public class LibraryBasePage : ContentPage
{
// no overriding of OnAppearing is done
}
## After the change:
public class LibraryBasePage : ContentPage
{
override OnAppearing()
{
base.OnAppearing();
FixSomething();
}
}
A new version of the UI library is released. The new release is integrated in the base app, while the modules that are referenced in the base app still reference an older version of the UI library.
In cases where a page from a module inherits the LibraryBasePage
and overrides OnAppearing, we notice that the fix which was added to the LibraryBasePage
does not work.
public class ModuleBasePage : LibraryBasePage
{
override OnAppearing()
{
base.OnAppearing();
...
}
}
Only when updating to the newest UI library, both inside the module and in the base app, the fix works.
Why is it not sufficient updating the UI library just in the base app? Our assumption was that the NuGet dependency resolution uses the fixed UI library version because it is on top of the dependency graph. When debugging the ModuleBasePage, the LibraryBasePage.OnAppearing method is never called. Our feeling is that this behavior can be explained with how vtables are created and used. How does the vtable of ModuleBasePage look like, when (a) updating to new release of the UI Library in modules and base app and (b) using only the new release of the UI lib in the base app?
The problem appears to be that base.OnAppearing();
refers directly to ContentPage.OnAppearing
, not to LibraryBasePage.OnAppearing
, because it did not exist when the module was built.
base.
does not use callvirt
, it uses call
, so no lookup on the vtable is done. Given the newer version is actually loaded into the app, I think this is technically unverifiable, but it seems no error is actually thrown.
As you can see from this sharplab, the line of code is calling directly to ContentPage
call instance void ContentPage::OnAppearing2()
The only way to prevent this is to have every virtual
function specifically overridden, even if there is no intention at the moment of doing anything with it.
public class LibraryBasePage : ContentPage
{
override OnAppearing()
{
base.OnAppearing();
}
}
ECMA-335 Section III states:
3.19
call
– call a method..snip..
- It is valid to call a virtual method using
call
(rather thancallvirt
); this indicates that the method is to be resolved using the class specified by method rather than as specified dynamically from the object being invoked. This is used, for example, to compile calls to ―methods onsuper
(i.e., the statically known parent class)
Interestingly enough, the Verifiability section there does not specifically exclude the case of calling base.base.Method
, so it's unclear if it is actually unverifiable. C# certainly does not allow it.