使用代码编写 C# 和 Java 代码






4.88/5 (9投票s)
代码编写器允许透明地为 C# 和 Java 编写代码,用于序列化、相等性等应用程序。
引言
本文将介绍一个框架,该框架能够让您编写一次代码,然后几乎透明地为 C# 和 Java 生成相同的逻辑代码,并带有每种语言的特定语法。该框架允许您编写生成代码的代码:它公开了类、方法、循环等编程构造。
在此框架之上,您可以采用任何方法来创建所需代码。反射可能很常用,因为它允许根据现有类型的结构自动生成代码。
背景
在 C# 和 Java 之间编写接口时,一项常见任务是维护两个环境中相似的对象集,以及支持这些对象的序列化。有一些解决方案(如 Web 服务)可以用于此任务,但有时性能限制使其无法使用。即使我们使用一种技术,同样的限制也可能迫使我们编写自定义序列化代码,而不是使用内置的序列化,后者往往会创建大型序列化对象。
另一个常见需求是覆盖基本方法,如 Equals
、GetHashCode
、Clone
、ToString
等。很多时候,这可以概括为对所有对象属性执行几乎相同的操作,例如,在 Equals
中检查所有属性是否相等。我们可以使用反射来实现这一点,但除了反射的其他问题外,还可能存在性能影响。
该框架允许使用反射自动完成所有这些繁琐的任务,同时支持 C# 和 Java。
Using the Code
要使用的主要类是 CodeWriter
。它是一个 abstract
类,接收一个用于写入的 stream
,并公开所有允许您编写代码的操作。该类有两个实现:CsharpCodeWriter
和 JavaCodeWriter
,它们的功能正如您所料。
此外,Expressions 命名空间包含各种类,可用于不同操作中的元素。例如
codeWriter.WriteField(AccessModifier.Private, propertyType, field,
new ConstructorExpression(propertyType));
WriteField
操作会写入一个字段,并接收一个 ConstructorExpression
作为初始值。
现在我们将详细介绍此框架的两个用途。第一个用途非常简单:Equals
方法
private static void WriteEquals(CodeWriter codeWriter, Type type)
{
// Write the method signature
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);
// Check reference equality
LogicalBinaryExpression lbe = new LogicalBinaryExpression
(obj, LogicalBinaryOperator.EQUAL, StringExpression.THIS);
codeWriter.WriteIfStart(lbe);
codeWriter.WriteReturn(StringExpression.TRUE);
codeWriter.EndBlock();
codeWriter.WriteNewLine();
// Check for null and type
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();
// Convert the object
StringExpression realObj = new StringExpression("realObj");
codeWriter.WriteVariableDeclaration(type, realObj, new CastExpression(type, obj));
// Check each and every field
foreach (PropertyInfo property in type.GetProperties())
{
Type propertyType = property.PropertyType;
StringExpression propertyName = new StringExpression(property.Name);
string equalsMethodName = "Equals";
// C# List equals check reference equality,
// while Java check for members equality.
// We need to use SequenceEqual extension method in C#
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);
// End method
codeWriter.EndBlock();
}
初看之下可能有点复杂,但您会发现它实际上并不复杂。此方法的参数是编写器,以及一个用于实现 Equals
的对象类型。使用编写器,我编写了通常用于 Equals
方法的代码:我检查引用是否相等,然后检查给定对象是否不为 null
,并且它是否为正确的类型,之后,我只需检查两个对象中每个属性是否 equals
。结果
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;
}
一旦有了基础代码,您就会发现编写生成它的代码非常容易。考虑一旦完成这项工作,可以避免多少枯燥乏味的工作。还要注意,此方法实际上适用于您提供的每种类型,因为它使用反射来发现类型的属性。
一些小细节:在 Java 中,List
的 equals
方法是通过调用列表中每个成员的 equals
来实现的。在 C# 中,这是使用默认的引用相等性实现的。我喜欢 Java 的实现,而且幸运的是 C# 有一个名为 SequenceEqual
的扩展方法可以做到这一点。这是一个很好的例子,说明有时需要在此框架之上进行一些扩展来支持其中一种编程语言。如果您还没有注意到,已经为您完成了方法和类型名称的转换。该框架知道将所有方法名称转换为 Java 约定(首字母小写)。JavaCodeWriter
还接受字典,用于将 C# 类型和方法转换为 Java 类型和方法。例如,C# 的 GetHashCode
和 Java 的 hashCode
之间存在内置转换,C# 的所有原始类型和 Java 的原始类型之间也存在内置转换(bool=boolean
等)。
现在来看一个稍微复杂一点的例子,序列化
private static void WriteSerialization(CodeWriter codeWriter, Type type)
{
// Write method signature
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);
// Write all properties
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
Type propertyType = propertyInfo.PropertyType;
string propertyName = propertyInfo.Name;
GetPropertyExpression property =
new GetPropertyExpression(obj, new StringExpression(propertyName));
// Write list
if (IsGenericList(propertyType))
{
ValueExpression size = codeWriter.CreateListSize(property);
Type enumerableType = propertyType.GetGenericArguments()[0];
WriteEnumerableSerialization
(codeWriter, writer, size, enumerableType, property);
}
// Write non list
else
{
WritePropertySerialization(property, propertyType, writer, codeWriter);
}
}
// End method
codeWriter.EndBlock();
}
private static void WriteEnumerableSerialization(CodeWriter codeWriter,
StringExpression writer,
ValueExpression size,
Type enumerableType,
ValueExpression property)
{
// Write the list size
string methodName = GetWriteMethodName(codeWriter, enumerableType);
codeWriter.WriteExpression(new CallMethodExpression(writer, methodName, size));
// Write each member of the list
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)
{
// Java DataOutputStream doesn't supply a decent method for serializing string,
// so we have to do it on our own
string typeName = type.Name;
if (type == typeof(string) && codeWriter is JavaCodeWriter)
{
// Write the string length
ValueExpression stringLength = new CallMethodExpression(property, "length");
WritePropertySerialization(stringLength, typeof(int), writer, codeWriter);
// Write the string as bytes if it's not empty
codeWriter.WriteIfStart(new LogicalBinaryExpression
(stringLength, LogicalBinaryOperator.GREATER_THEN, new StringExpression("0")));
codeWriter.WriteExpression(new CallMethodExpression
(writer, "writeBytes", property));
codeWriter.EndBlock();
}
// Simple serialization is serialization we can do
// "inline" using the serialization writer methods
else if (IsSimpleSerialization(type))
{
ValueExpression ve;
// Enum is serialized as int
if (type.IsEnum)
{
ve = new CastExpression(typeof(int), property);
}
// Date ticks are serialized as long
else if (type == typeof(DateTime))
{
ve = new GetPropertyExpression(property, new StringExpression
(codeWriter is CSharpCodeWriter ? "Ticks" : "Time"));
}
// Each other simple property is serialized as is
else
{
ve = property;
}
// Use the appropriate method to serialize the property
codeWriter.WriteExpression(new CallMethodExpression
(writer, GetWriteMethodName(codeWriter, type), ve));
}
// Composite property, uses method signature that assumes
// we generate the serialization of this type using the generator as well
else
{
codeWriter.WriteExpression(new CallMethodExpression
("Serialize", property, writer));
}
}
有很多细节,但原理是一样的。我所做的是创建一个方法,该方法接收一个执行序列化的对象和一个要序列化的对象。在 C# 中,我选择使用 BinaryWriter
进行序列化,在 Java 中,我使用 DataOutputStream
。在创建 JavaCodeWriter
时,我传递了这两种类型之间的转换。完成之后,我需要做的就是序列化每一个属性。根据属性的类型,有一些特殊情况
List
- 我会序列化列表的大小,然后根据成员类型序列化每个成员String
- 在 C# 中,我可以使用BinaryWriter.Write
方法。在 Java 中没有这样的方法。我需要序列化string
的大小,然后将string
本身作为字节数组序列化Enum
- 序列化为整数DateTime
- 序列化长的Ticks
属性。在 Java 中,这个类叫做Date
,它有一个类似的属性叫做Time
- 基本类型 - 使用
BinaryWriter.Write
方法进行序列化。在 Java 中,每种类型都有自己的方法,例如DataOutputStream.writeInt
- 任何其他对象 - 我调用该方法,其签名假定序列化将使用相同的解决方案来实现
结果
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());
}
为如此少的序列化代码生成了大量的代码。当然,这对于我们使用的真实对象来说并不完全准确。一个小小说明:上面的代码不起作用。它可以工作,但首先需要做一些事情。Java 中的字节顺序与 C# 中的字节顺序相反。这意味着,如果您写入一个 short 值,它由 2 个字节组成,那么对于每种技术,您将以不同的顺序获得这两个字节。我们为这个问题找到的解决方案是继承 BinaryWriter
,并使用 IPAddress.HostToNetworkOrder
来写入数字。
结论
除了上面的代码,您还可以获得反序列化、GetHashCode
的实现,以及将 C# enum
和类写入 Java enum
和类的代码。您应该能够轻松实现其他方法,如 ToString
和 Clone
。框架和示例都已记录在案。
在此框架的实现过程中,从某个点开始,添加其他构造变得非常容易,所以如果您缺少 while
循环或 ?
: 运算符等内容,您应该能够轻松地添加它们。总之,我希望您发现这很有用。