I've already mentioned that we identified early on that generics would be a
problem, and one of the steps we took was to reduce unnecessary and unproductive
use of generic types. In fact, we have almost totally eliminated all use of
generics in Saxon-defined classes, which was the major problem. That leaves
generics in system-defined classes (notably the collection classes such as
List<T>
) which we can't easily manage without.
In fact, most uses of these classes translate from Java to C# without trouble. But there are still a few difficulties:
Diamond Operators
Java allows you to write List<X> list = new ArrayList<>()
(referred
to as a diamond operator, though it's not technically an operator). In C# it has
to be new ArrayList<X>()
. So we need to work out what X is – essentially by
applying the same type inferencing rules that the Java compiler applies. The way
we do this is by recognising common cases: object instantiation on the right-hand
side of an assignment, in a return clause, in an argument to a non-polymorphic
method, etc. The logic is quite complex, and it catches perhaps 95% of cases. The
remainder are handled by changing the Java code: either by introducing a variable,
or by adding the type redundantly within the diamond.
XSLT template rules really come into their own here. We handle about a dozen patterns where the type of the parameter can be inferred, and each of these is represented by a template rule. As we get smarter or discover more cases, we can simply add more template rules. Here's an example of one of the rules:
<xsl:template match="*[@nodeType='ReturnStmt'] [ancestor::member[1]/type/@RESOLVED_TYPE]/*"> <xsl:variable name="type" select="ancestor::member[1]/type/@RESOLVED_TYPE" as="xs:string"/> <xsl:value-of select="f:extract-type-arguments($type)"/> </xsl:template>
This rule detects a diamond operator appearing in a return statement (the rule appears
in a module with default mode diamond
, which is only used to process expressions
that have already been recognised as containing a diamond operator). It finds the ancestor
method declaration (ancestor::member[1]
), determines the declared type of the
method result, and inserts that into the C# code as the type parameter in place of the
diamond operator.
Wildcards
The Java wildcard constructs <? extends T>
and <? super T>
have no direct equivalent
in C#. The way we handle these depends on where they are used. The default action of the
converter is just to replace them with <T>
, which often works. But in class and method
declarations we generate a C# where
clause to constrain the type bounds, so
public class GroundedValueAsIterable<T extends Item> implements Iterable<T> {...}
becomes
public class GroundedValueAsIterable<T> : IEnumerable<T> where T : Item {...}
One issue we face is that the default type Object
in Java is less all-embracing
than the object type in C#: the former does not include primitive types such as int
or double, the latter does. This means that where the required type is Object
, the
supplied value can be null; but this is not so in C#, because primitive types do
not allow a null. This permeates the design of collection classes. Often the solution
is to constrain the C# class to handle reference types only, using the clause where T : class
.