As we've already mentioned, C# requires any method that is overridden to be
declared virtual, and any method that overrides another to be declared
with the modifier override.
We handle this by analyzing the class hierarchy and recording the analysis in the
digest XML file, which is available to the stylesheet that generates the C# code.
In addition, Java allows an overriding method to have a covariant return type:
if Expression.evaluate() returns Sequence, then Arithmetic.evaluate() can return
AtomicValue, given that AtomicValue is a subclass of Sequence. C# doesn't allow
covariant return types until version 9.0 of the language, and we decided this was a new promised
feature that we would be unwise to rely on. Instead:
when we're analyzing the class hierarchy, we detect any use of covariance, and change the overriding method to use the same return type as its base method;
when we're analyzing the class hierarchy, we detect any use of covariance, and change the overriding method to use the same return type as its base method when we find a call to a method that's been overridden with a covariant return type, we insert a cast so the expected type remains as it was.
Java allows interfaces to define default implementations for methods; C# does not. The transpiler handles default method implementations by copying them into each subclass. This of course can lead to a lot of code duplication, so we have eliminated some of the cases where we were using default methods unnecessarily.