Introduction
This article will introduce a framework that will make it possible to write a piece of code once, and almost transparently generate the same logical code for both C# and Java, with the specific syntax for each language. The framework lets you write code that generates code: it exposes programming constructs such as classes, methods, loops, etc.
On top on this framework, one may use any method to create the code that he or she wants. Reflection will probably be in common use since it allows to automate the code generation according to the structure of existing types.
Background
A common task when writing an interface between C# and Java is to maintain a similar set of objects in both environments, as well as supporting serialization for those objects. There are solutions like web services for this task, but sometimes performance constraints make them impossible to use. Even when we use one technology, the same constraints may force us to write a custom serialization code, rather than using the built in serialization that tend to create large serialized objects.
Another common requirement is overriding basic methods like Equals
, GetHashCode
, Clone
, ToString
, etc. Many times, this is summed up in doing almost the exact operation on all object properties, e.g., checking the equality of all properties in Equals
. We can use reflection for this, but there may be a performance impact, besides the other problems with reflection.
The framework allows to automatically do all of these tedious tasks using reflection for both C# and Java.
Using the Code
The main Class to use is called CodeWriter
. It's an abstract
class that receives a stream
to write to, and exposes all the operations that allows you to write the code. There are 2 implementations of this class: CsharpCodeWriter
and JavaCodeWriter
, that do what you might expect.
On top of that, the Expressions namespace contains all sorts of classes that can be used as elements in the different operations. For example:
codeWriter.WriteField(AccessModifier.Private, propertyType, field,
new ConstructorExpression(propertyType));
The WriteField
operation writes a field, and receives a ConstructorExpression
as initial value.
We'll now go into details with 2 uses I found for this framework. The first one is quite simple: Equals
method:
private static void WriteEquals(CodeWriter codeWriter, Type type)
{
StringExpression obj = new StringExpression("obj");
IDictionary<StringExpression, Type> parameters =
new Dictionary<StringExpression, Type>();
parameters.Add(obj, typeof(object));
codeWriter.StartMethod(AccessModifier.Public,
MethodModifier.Override, typeof(bool), "Equals", parameters);
LogicalBinaryExpression lbe = new LogicalBinaryExpression
(obj, LogicalBinaryOperator.EQUAL, StringExpression.THIS);
codeWriter.WriteIfStart(lbe);
codeWriter.WriteReturn(StringExpression.TRUE);
codeWriter.EndBlock();
codeWriter.WriteNewLine();
LogicalNnaryExpression lne = new LogicalNnaryExpression();
lne.Add(new LogicalBinaryExpression(obj, LogicalBinaryOperator.EQUAL,
StringExpression.NULL), LogicalNnaryOperator.OR);
lne.Add(new NotExpression(new LogicalBinaryExpression(obj,
LogicalBinaryOperator.IS, new StringExpression(type.Name))));
codeWriter.WriteIfStart(lne);
codeWriter.WriteReturn(StringExpression.FALSE);
codeWriter.EndBlock();
codeWriter.WriteNewLine();
StringExpression realObj = new StringExpression("realObj");
codeWriter.WriteVariableDeclaration(type, realObj, new CastExpression(type, obj));
foreach (PropertyInfo property in type.GetProperties())
{
Type propertyType = property.PropertyType;
StringExpression propertyName = new StringExpression(property.Name);
string equalsMethodName = "Equals";
if ((IsGenericList(propertyType)) && codeWriter is CSharpCodeWriter)
{
equalsMethodName = "SequenceEqual";
}
NotExpression ne = new NotExpression
(new CallMethodExpression(new GetPropertyExpression(propertyName),
equalsMethodName,
new GetPropertyExpression(realObj, propertyName)));
codeWriter.WriteIfStart(ne);
codeWriter.WriteReturn(StringExpression.FALSE);
codeWriter.EndBlock();
}
codeWriter.WriteReturn(StringExpression.TRUE);
codeWriter.EndBlock();
}
It may seem a little complex at first, but you'll find out that it really isn't. The parameters for this method are the writer, and a type to implement Equals
for. Using the writer, I write the usual code I'll write for Equals
method: I check if the references are equals, then I check if the given object is not null
, and if it's of the correct type, and after that, I just check that each property is equals
in both objects. The result:
C#
public override bool Equals(Object obj)
{
if(obj == this)
{
return true;
}
if(obj == null || !(obj is MyObject))
{
return false;
}
MyObject realObj = (MyObject)obj;
if(!(MyField.Equals(realObj.MyField)))
{
return false;
}
if(!(MyObjectField.Equals(realObj.MyObjectField)))
{
return false;
}
if(!(MyListField.SequenceEqual(realObj.MyListField)))
{
return false;
}
if(!(MyEnumField.Equals(realObj.MyEnumField)))
{
return false;
}
if(!(MyDateTimeField.Equals(realObj.MyDateTimeField)))
{
return false;
}
return true;
}
Java
public boolean equals(Object obj) {
if(obj == this) {
return true;
}
if(obj == null || !(obj instanceof MyObject)) {
return false;
}
MyObject realObj = (MyObject)obj;
if(!(getMyField().equals(realObj.getMyField()))) {
return false;
}
if(!(getMyObjectField().equals(realObj.getMyObjectField()))) {
return false;
}
if(!(getMyListField().equals(realObj.getMyListField()))) {
return false;
}
if(!(getMyEnumField().equals(realObj.getMyEnumField()))) {
return false;
}
if(!(getMyDateTimeField().equals(realObj.getMyDateTimeField()))) {
return false;
}
return true;
}
Once you have a basic code, you'll discover that it's really easy to write a code that generates it. Consider how much boring and tedious work is avoided once you do it. Also note that this method actually works for each type you'll give it, since it's using reflection to discover the type properties.
Some smalls details: In Java, List
's equals
method is implemented by calling equals
for each member in the list. In C#, this is implemented using default reference equality. I like the Java implementation, and luckily C# has an extension method called SequenceEqual
that does just that. This is a good example to cases where something needs to be added on top of the framework to support one of the programming languages. What is already done for you, if you haven't noticed, is conversion of method and type names. The framework knows to convert all method names to Java convention (First lowercase letter). JavaCodeWriter
also accepts dictionaries that convert C# types and methods to Java ones. For example, there is a built in conversion between C# GetHashCode
and Java hashCode
, and between all C# primitive types to Java primitive types (bool=boolean
, etc.).
Now for a slightly more complicated example, serialization:
private static void WriteSerialization(CodeWriter codeWriter, Type type)
{
StringExpression obj = new StringExpression("obj");
StringExpression writer = new StringExpression("writer");
IDictionary<StringExpression, Type> parameters =
new Dictionary<StringExpression, Type>();
string typeName = type.Name;
parameters.Add(obj, type);
parameters.Add(writer, typeof(BinaryWriter));
codeWriter.StartMethod(AccessModifier.Public, "Serialize", parameters);
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
Type propertyType = propertyInfo.PropertyType;
string propertyName = propertyInfo.Name;
GetPropertyExpression property =
new GetPropertyExpression(obj, new StringExpression(propertyName));
if (IsGenericList(propertyType))
{
ValueExpression size = codeWriter.CreateListSize(property);
Type enumerableType = propertyType.GetGenericArguments()[0];
WriteEnumerableSerialization
(codeWriter, writer, size, enumerableType, property);
}
else
{
WritePropertySerialization(property, propertyType, writer, codeWriter);
}
}
codeWriter.EndBlock();
}
private static void WriteEnumerableSerialization(CodeWriter codeWriter,
StringExpression writer,
ValueExpression size,
Type enumerableType,
ValueExpression property)
{
string methodName = GetWriteMethodName(codeWriter, enumerableType);
codeWriter.WriteExpression(new CallMethodExpression(writer, methodName, size));
StringExpression listObj = new StringExpression("listObj");
codeWriter.StartForEachLoop(enumerableType, listObj, property);
WritePropertySerialization(listObj, enumerableType, writer, codeWriter);
codeWriter.EndBlock();
}
private static void WritePropertySerialization
(ValueExpression property, Type type, StringExpression writer, CodeWriter codeWriter)
{
string typeName = type.Name;
if (type == typeof(string) && codeWriter is JavaCodeWriter)
{
ValueExpression stringLength = new CallMethodExpression(property, "length");
WritePropertySerialization(stringLength, typeof(int), writer, codeWriter);
codeWriter.WriteIfStart(new LogicalBinaryExpression
(stringLength, LogicalBinaryOperator.GREATER_THEN, new StringExpression("0")));
codeWriter.WriteExpression(new CallMethodExpression
(writer, "writeBytes", property));
codeWriter.EndBlock();
}
else if (IsSimpleSerialization(type))
{
ValueExpression ve;
if (type.IsEnum)
{
ve = new CastExpression(typeof(int), property);
}
else if (type == typeof(DateTime))
{
ve = new GetPropertyExpression(property, new StringExpression
(codeWriter is CSharpCodeWriter ? "Ticks" : "Time"));
}
else
{
ve = property;
}
codeWriter.WriteExpression(new CallMethodExpression
(writer, GetWriteMethodName(codeWriter, type), ve));
}
else
{
codeWriter.WriteExpression(new CallMethodExpression
("Serialize", property, writer));
}
}
There are many details, but the principal stays the same. What I do is to create a method that receives an object that does the serialization, and the object to serialize. In C# case, I chose to use BinaryWriter
for serialization, and in Java I use DataOutputStream
. I passed the conversion between this 2 types when I created the JavaCodeWriter
. After that is done, all I need to do is to serialize each and every property. There are some cases based on the type of the property:
List
- I serialize the size, and then serialize each member according to the members type String
- In C#, I can use the BinaryWriter.Write
method. No such luck in Java. I have to serialize the string
size, and then to serialize the string
itself as byte array Enum
- Serialized as integer DateTime
- The long Ticks
property is serialized. In Java, this class is called Date
, and it has a similar property named Time
- Primitive types - Serialized using the
BinaryWriter.Write
method. In Java, each type has its own method, e.g. DataOutputStream.writeInt
- Any other object - I call the method with signature assuming that serialization will be implemented using the same solution
And the result:
C#
public void Serialize(MyObject obj, BinaryWriter writer)
{
writer.Write(obj.MyField);
Serialize(obj.MyObjectField, writer);
writer.Write(obj.MyListField.Count);
foreach(long listObj in obj.MyListField)
{
writer.Write(listObj);
}
writer.Write((int)obj.MyEnumField);
writer.Write(obj.MyDateTimeField.Ticks);
}
Java
public void serialize(MyObject obj, DataOutputStream writer) {
writer.writeInt(obj.getMyField());
serialize(obj.getMyObjectField(), writer);
writer.writeLong(obj.getMyListField().size());
for(long listObj : obj.getMyListField()) {
writer.writeLong(listObj);
}
writer.writeInt((int)obj.getMyEnumField());
writer.writeLong(obj.getMyDateTimeField().getTime());
}
So much generation code for so little serialization code. Of course, this is not true to the real objects that we use. One itty bitty comment: the above code doesn't work. It can work, but something has to be done first. The byte order in Java is opposite to the byte order in C#. This means that if you write a short value, which is composed of 2 bytes, you will get the 2 bytes in different order for each technology. The solution we found for this problem is inheriting BinaryWriter
, and using IPAddress.HostToNetworkOrder
to write numbers.
Conclusion
Besides the above code, you can get implementations for deserialization, GetHashCode
, as well as code that writes the C# enum
s and classes to Java enum
s and classes. You should be able to easily implement additional methods like ToString
and Clone
. The framework and the examples are all documented.
From some point during the implementation of this framework, it became very easy to add additional constructs, so if you miss things like while
loop or ?
: operator you should be able to easily add those as well. In any case, I hope you found this useful.