65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (9投票s)

2010 年 6 月 28 日

CPOL

5分钟阅读

viewsIcon

35530

downloadIcon

384

代码编写器允许透明地为 C# 和 Java 编写代码,用于序列化、相等性等应用程序。

引言

本文将介绍一个框架,该框架能够让您编写一次代码,然后几乎透明地为 C# 和 Java 生成相同的逻辑代码,并带有每种语言的特定语法。该框架允许您编写生成代码的代码:它公开了类、方法、循环等编程构造。

在此框架之上,您可以采用任何方法来创建所需代码。反射可能很常用,因为它允许根据现有类型的结构自动生成代码。

背景

在 C# 和 Java 之间编写接口时,一项常见任务是维护两个环境中相似的对象集,以及支持这些对象的序列化。有一些解决方案(如 Web 服务)可以用于此任务,但有时性能限制使其无法使用。即使我们使用一种技术,同样的限制也可能迫使我们编写自定义序列化代码,而不是使用内置的序列化,后者往往会创建大型序列化对象。

另一个常见需求是覆盖基本方法,如 EqualsGetHashCodeCloneToString 等。很多时候,这可以概括为对所有对象属性执行几乎相同的操作,例如,在 Equals 中检查所有属性是否相等。我们可以使用反射来实现这一点,但除了反射的其他问题外,还可能存在性能影响。

该框架允许使用反射自动完成所有这些繁琐的任务,同时支持 C# 和 Java。

Using the Code

要使用的主要类是 CodeWriter。它是一个 abstract 类,接收一个用于写入的 stream ,并公开所有允许您编写代码的操作。该类有两个实现:CsharpCodeWriterJavaCodeWriter,它们的功能正如您所料。

此外,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 中,Listequals 方法是通过调用列表中每个成员的 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 循环或 ? : 运算符等内容,您应该能够轻松地添加它们。总之,我希望您发现这很有用。

© . All rights reserved.