SlideShare a Scribd company logo
Using Reflections and Automatic
Code Generation
Ivan Dolgushin
Game Developer
Appturn
It is Very Technical:
• A lot of code
• Only 5 pictures
• About not writing code
Cloning
namespace System
{
[ComVisible (true)]
public interface ICloneable
{
//
// Methods
//
object Clone ();
}
}
#region ICloneable implementation
public virtual object Clone()
{
return new BaseClass(this);
}
#endregion
Manual Implementation
#region ICloneable implementation
public override object Clone()
{
return new DerivedClass(this);
}
#endregion
Manual Implementation
#region ICloneable implementation
public virtual object Clone()
{
return new BaseClass(this);
}
#endregion
#region ICloneable implementation
public override object Clone()
{
return new DerivedClass(this);
}
#endregion
Manual Implementation
System.Activator
#region ICloneable implementation
public object Clone()
{
return Activator
.CreateInstance(
this.GetType(),
this
);
}
#endregion
Factory
public static BaseEffect CreateEffect(
Creature owner, Creature caster,
EEffectType effectType, byte effectlLevel)
{
var effectInfo = ObjectsConfig.Instance
.GetEffectInfo(effectType);
return (BaseEffect)Activator
.CreateInstance(
Type.GetType(effectInfo.TypeName),
owner, caster,
effectType, effectlLevel);
}
System.Reflection
#region ICloneable implementation
public object Clone()
{
return this
.GetType().GetConstructor(
new Type[] { this.GetType() }
).Invoke(new object[] { this });
}
#endregion
Field Value Comparison
Data Storage Classes
public class ObjectBaseLevelInfo
{
public int RequiredObjectLevel = 0;
public int LaboratoryUpgradeCost = 0;
public uint LaboratoryUpgradeDuration = 0;
public int UpgradeCost = 0;
public uint UpgradeDuration = 0;
public int Hp = 0;
public int XpAmount = 0;
public int VisualLevel = 1;
}
Data Storage Classes
public class MonsterLevelInfo
: ObjectBaseLevelInfo
{
public int Xp = 0;
public float MovementSpeed = 0f;
public byte MinLvlDrop = 0;
public byte MaxLvlDrop = 0;
public float DropChance = 0f;
}
Manual Comparator
public override bool Equals(object obj)
{
MonsterLevelInfo other
= obj as MonsterLevelInfo;
return null != obj
&& base.Equals(obj)
&& Xp == other.Xp
&& MovementSpeed == other.MovementSpeed
&& MinLvlDrop == other.MinLvlDrop
&& MaxLvlDrop == other.MaxLvlDrop
&& DropChance == other.DropChance;
}
System.Reflection
public static bool ValueEqual(this object a, object b)
{
if (object.ReferenceEquals(a, b)) return true;
Type typeA = a.GetType();
if (typeA != b.GetType()) return false;
foreach (FieldInfo fi in typeA.GetFields(
BindingFlags.Public |
BindingFlags.GetField |
BindingFlags.GetProperty |
BindingFlags.FlattenHierarchy))
{
if (fi.GetValue(a) != fi.GetValue(b))
return false;
}
return true;
}
Let’s Add More Control
[AttributeUsage(AttributeTargets.Field)]
public class DontCompareAttribute : Attribute {}
public static bool ValueEqual(this object a, object b)
{
...
foreach (FieldInfo fi in typeA.GetFields(...))
{
if (fi.GetCustomAttributes(
typeof(DontCompareAttribute),
true).Length == 0
&& fi.GetValue(a) != fi.GetValue(b))
return false;
}
return true;
}
Data Storage Class
public class MonsterLevelInfo
: ObjectBaseLevelInfo
{
public int Xp = 0;
public float MovementSpeed = 0f;
public byte MinLvlDrop = 0;
public byte MaxLvlDrop = 0;
[DontCompare]
public float DropChance = 0f;
}
Data Storage Class
[Serializable]
[XmlType("MonsterLevel")]
public class MonsterLevelInfo : ObjectBaseLevelInfo
{
[XmlAttribute]
public int Xp = 0;
[XmlAttribute]
public float MovementSpeed = 0f;
[XmlAttribute]
public byte MinLvlDrop = 0;
[XmlAttribute]
public byte MaxLvlDrop = 0;
[XmlAttribute]
[DontCompare]
public float DropChance = 0f;
}
System.CodeDom
public static void GenerateComparators()
{
CodeCompileUnit targetUnit;
CodeTypeDeclaration targetClass;
Assembly assembly = GetAssemblies().First(a => a.GetName().Name == "Assembly-CSharp");
foreach (Type type in assembly.GetTypes())
{
object[] genAttributes = type.GetCustomAttributes(typeof(GenerateComparatorAttribute), false);
if (genAttributes.Length > 0)
{
targetUnit = new CodeCompileUnit();
CodeNamespace ns = new CodeNamespace(type.Namespace);
ns.Imports.Add(new CodeNamespaceImport("System"));
targetClass = new CodeTypeDeclaration(type.Name);
targetClass.IsClass = true; targetClass.IsPartial = true;
targetClass.TypeAttributes = TypeAttributes.Public;
ns.Types.Add(targetClass);
targetUnit.Namespaces.Add(ns);
targetClass.Members.Add(GenerateComparator(type));
using (CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp")) {
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BracingStyle = "C";
string outputFileName = Path.Combine(outputDirName, type.FullName + ".Comparator.cs");
using (StreamWriter sourceWriter = new StreamWriter(outputFileName))
{
provider.GenerateCodeFromCompileUnit(targetUnit, sourceWriter, options);
}
}
}
}
}
System.CodeDom
foreach (FieldInfo fi in type.GetFields(...))
{
var checkExpr = new CodeBinaryOperatorExpression(
new CodeFieldReferenceExpression(thisRef, fi.Name),
CodeBinaryOperatorType.ValueEquality,
new CodeFieldReferenceExpression(argRef, fi.Name));
var appendExpr = new CodeBinaryOperatorExpression(
retRef, CodeBinaryOperatorType.BooleanAnd, checkExpr);
CodeAssignStatement checkStatement =
new CodeAssignStatement(retRef, appendExpr);
compareMethod.Statements.Add(checkStatement);
}
Call It With Unity Menu
public static partial class CodeGeneration
{
[MenuItem("Tools/Code Generation/Comparators",
false, 1103)]
public static void GenerateComparators()
{
Comparer.GenerateComparators();
}
}
WYG
public partial class MonsterLevelInfo
{
public override bool Equals(object obj)
{
bool rv = true;
MonsterLevelInfo other=obj as MonsterLevelInfo;
if ((other == null)) return false;
rv = (rv && (this.Xp == other.Xp));
...
rv = (rv && (this.Hp == other.Hp));
rv = (rv && (this.XpAmount == other.XpAmount));
return rv ;
}
}
Enum Converter
Generated Convert Method
public static EResearch MonsterToResearchType(EObject v)
{
EResearchType rv = default(EResearchType);
switch (v) {
case EObject.Cobra: rv = EResearch.Cobra; break;
case EObject.Crab: rv = EResearch.Crab; break;
case EObject.Kobold: rv = EResearch.Kobold; break;
case EObject.Orc: rv = EResearch.Orc; break;
case EObject.Shaman: rv = EResearch.Shaman; break;
case EObject.Spider: rv = EResearch.Spider; break;
case EObject.Imp: rv = EResearch.Imp; break;
}
return rv;
}
System.CodeDom
System.CodeDom
StringBuilder switchBuilder = new StringBuilder();
switchBuilder.AppendLine("switch (v) {");
foreach(var val in Enum.GetNames(sourceEnum))
{
if (Enum.IsDefined(destEnum, val))
{
switchBuilder.AppendFormat(
"case {0}.{2}: retValue = {1}.{2}; ",
sourceEnum, destEnum, val);
switchBuilder.AppendLine("break;");
}
}
convertMethod.Statements.Add(
new CodeSnippetStatement(switchBuilder));
Xml Serialization
XML Serializer Generator Tool
(Sgen.exe)
The XML Serializer Generator creates an XML
serialization assembly for types in a specified
assembly in order to improve the startup
performance of a XmlSerializer when it serializes
or deserializes objects of the specified types.
Sgen.exe
rm -R *.cs
sgen --assembly:Assembly-CSharp.dll 
--reference:UnityEngine.dll 
--verbose --force --keep 
--type:PaperPath.ObjectBaseLevelInfo 
--type:PaperPath.MonsterLevelInfo 
...
--type:PaperPath.CharacterLevelInfo 
result=$(find . -name *.cs)
mv $result ../Assets/Classes/GeneratedSerializers.cs
rm Assembly-CSharp.XmlSerializers.dll
Do It with Unity
[MenuItem("Tools/Code Generation/Serializers”)]
public static void RegenerateSerializers()
{
System.IO.Directory.CreateDirectory(OutDir);
CompileAssemblies();
PrepareSGen();
GenerateSerializers();
System.IO.Directory.Delete(OutDir, true);
}
Compile
public static void CompileAssemblies()
{
HandleExecutable he = new HandleExecutable();
he.OutputHandler = Log;
he.ErrorHandler = LogError;
string Configuration = "Debug";
string Solution = "Client.sln";
he.CallExecutable("xbuild"
, string.Format("/property:OutDir={0}
/p:Configuration={1} {2}",
OutDir, Configuration, Solution)
, ””, true);
}
Prepare Script
public static string GetScript()
{
StringBuilder sgenScript = new StringBuilder();
sgenScript.AppendLine("rm -R *.cs");
sgenScript.AppendFormat("sgen --assembly:{0} --reference:{1}” +
” --force –keep”, AssemblyPath, DependencyPaths);
foreach (Type type in GetAssembly().GetTypes())
{
object[] genAttributes = type.GetCustomAttributes(
typeof(GenerateSerializersAttribute), false);
if (genAttributes.Length > 0)
sgenScript.AppendLine(" --type:" + type.FullName + " ");
}
sgenScript.AppendLine();
sgenScript.AppendLine("result=$(find . -name *.cs)");
sgenScript.AppendLine("mv $result ../" + GeneratedSerializersPath);
sgenScript.AppendLine("rm Assembly-CSharp.XmlSerializers.dll");
return sgenScript;
}
Run Script and Postprocess
public static void GenerateSerializers()
{
HandleExecutable he = new HandleExecutable();
he.CallExecutable("sh”, "generate.sh”, OutDir, true);
string serializersCode = File.ReadAllText(GSPath);
serializersCode = serializersCode.Replace(
"(type.FullName)",
"(type.FullName.Replace("+", "."))");
File.WriteAllText(
GeneratedSerializersPath,
serializersCode);
}
Thank You!
Questions?
Feel free to contact me
ivand@appturn.com

More Related Content

Using Reflections and Automatic Code Generation

  • 1. Using Reflections and Automatic Code Generation Ivan Dolgushin Game Developer Appturn
  • 2. It is Very Technical: • A lot of code • Only 5 pictures • About not writing code
  • 4. namespace System { [ComVisible (true)] public interface ICloneable { // // Methods // object Clone (); } }
  • 5. #region ICloneable implementation public virtual object Clone() { return new BaseClass(this); } #endregion Manual Implementation
  • 6. #region ICloneable implementation public override object Clone() { return new DerivedClass(this); } #endregion Manual Implementation
  • 7. #region ICloneable implementation public virtual object Clone() { return new BaseClass(this); } #endregion #region ICloneable implementation public override object Clone() { return new DerivedClass(this); } #endregion Manual Implementation
  • 8. System.Activator #region ICloneable implementation public object Clone() { return Activator .CreateInstance( this.GetType(), this ); } #endregion
  • 9. Factory public static BaseEffect CreateEffect( Creature owner, Creature caster, EEffectType effectType, byte effectlLevel) { var effectInfo = ObjectsConfig.Instance .GetEffectInfo(effectType); return (BaseEffect)Activator .CreateInstance( Type.GetType(effectInfo.TypeName), owner, caster, effectType, effectlLevel); }
  • 10. System.Reflection #region ICloneable implementation public object Clone() { return this .GetType().GetConstructor( new Type[] { this.GetType() } ).Invoke(new object[] { this }); } #endregion
  • 12. Data Storage Classes public class ObjectBaseLevelInfo { public int RequiredObjectLevel = 0; public int LaboratoryUpgradeCost = 0; public uint LaboratoryUpgradeDuration = 0; public int UpgradeCost = 0; public uint UpgradeDuration = 0; public int Hp = 0; public int XpAmount = 0; public int VisualLevel = 1; }
  • 13. Data Storage Classes public class MonsterLevelInfo : ObjectBaseLevelInfo { public int Xp = 0; public float MovementSpeed = 0f; public byte MinLvlDrop = 0; public byte MaxLvlDrop = 0; public float DropChance = 0f; }
  • 14. Manual Comparator public override bool Equals(object obj) { MonsterLevelInfo other = obj as MonsterLevelInfo; return null != obj && base.Equals(obj) && Xp == other.Xp && MovementSpeed == other.MovementSpeed && MinLvlDrop == other.MinLvlDrop && MaxLvlDrop == other.MaxLvlDrop && DropChance == other.DropChance; }
  • 15. System.Reflection public static bool ValueEqual(this object a, object b) { if (object.ReferenceEquals(a, b)) return true; Type typeA = a.GetType(); if (typeA != b.GetType()) return false; foreach (FieldInfo fi in typeA.GetFields( BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.FlattenHierarchy)) { if (fi.GetValue(a) != fi.GetValue(b)) return false; } return true; }
  • 16. Let’s Add More Control [AttributeUsage(AttributeTargets.Field)] public class DontCompareAttribute : Attribute {} public static bool ValueEqual(this object a, object b) { ... foreach (FieldInfo fi in typeA.GetFields(...)) { if (fi.GetCustomAttributes( typeof(DontCompareAttribute), true).Length == 0 && fi.GetValue(a) != fi.GetValue(b)) return false; } return true; }
  • 17. Data Storage Class public class MonsterLevelInfo : ObjectBaseLevelInfo { public int Xp = 0; public float MovementSpeed = 0f; public byte MinLvlDrop = 0; public byte MaxLvlDrop = 0; [DontCompare] public float DropChance = 0f; }
  • 18. Data Storage Class [Serializable] [XmlType("MonsterLevel")] public class MonsterLevelInfo : ObjectBaseLevelInfo { [XmlAttribute] public int Xp = 0; [XmlAttribute] public float MovementSpeed = 0f; [XmlAttribute] public byte MinLvlDrop = 0; [XmlAttribute] public byte MaxLvlDrop = 0; [XmlAttribute] [DontCompare] public float DropChance = 0f; }
  • 19. System.CodeDom public static void GenerateComparators() { CodeCompileUnit targetUnit; CodeTypeDeclaration targetClass; Assembly assembly = GetAssemblies().First(a => a.GetName().Name == "Assembly-CSharp"); foreach (Type type in assembly.GetTypes()) { object[] genAttributes = type.GetCustomAttributes(typeof(GenerateComparatorAttribute), false); if (genAttributes.Length > 0) { targetUnit = new CodeCompileUnit(); CodeNamespace ns = new CodeNamespace(type.Namespace); ns.Imports.Add(new CodeNamespaceImport("System")); targetClass = new CodeTypeDeclaration(type.Name); targetClass.IsClass = true; targetClass.IsPartial = true; targetClass.TypeAttributes = TypeAttributes.Public; ns.Types.Add(targetClass); targetUnit.Namespaces.Add(ns); targetClass.Members.Add(GenerateComparator(type)); using (CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp")) { CodeGeneratorOptions options = new CodeGeneratorOptions(); options.BracingStyle = "C"; string outputFileName = Path.Combine(outputDirName, type.FullName + ".Comparator.cs"); using (StreamWriter sourceWriter = new StreamWriter(outputFileName)) { provider.GenerateCodeFromCompileUnit(targetUnit, sourceWriter, options); } } } } }
  • 20. System.CodeDom foreach (FieldInfo fi in type.GetFields(...)) { var checkExpr = new CodeBinaryOperatorExpression( new CodeFieldReferenceExpression(thisRef, fi.Name), CodeBinaryOperatorType.ValueEquality, new CodeFieldReferenceExpression(argRef, fi.Name)); var appendExpr = new CodeBinaryOperatorExpression( retRef, CodeBinaryOperatorType.BooleanAnd, checkExpr); CodeAssignStatement checkStatement = new CodeAssignStatement(retRef, appendExpr); compareMethod.Statements.Add(checkStatement); }
  • 21. Call It With Unity Menu public static partial class CodeGeneration { [MenuItem("Tools/Code Generation/Comparators", false, 1103)] public static void GenerateComparators() { Comparer.GenerateComparators(); } }
  • 22. WYG public partial class MonsterLevelInfo { public override bool Equals(object obj) { bool rv = true; MonsterLevelInfo other=obj as MonsterLevelInfo; if ((other == null)) return false; rv = (rv && (this.Xp == other.Xp)); ... rv = (rv && (this.Hp == other.Hp)); rv = (rv && (this.XpAmount == other.XpAmount)); return rv ; } }
  • 24. Generated Convert Method public static EResearch MonsterToResearchType(EObject v) { EResearchType rv = default(EResearchType); switch (v) { case EObject.Cobra: rv = EResearch.Cobra; break; case EObject.Crab: rv = EResearch.Crab; break; case EObject.Kobold: rv = EResearch.Kobold; break; case EObject.Orc: rv = EResearch.Orc; break; case EObject.Shaman: rv = EResearch.Shaman; break; case EObject.Spider: rv = EResearch.Spider; break; case EObject.Imp: rv = EResearch.Imp; break; } return rv; }
  • 26. System.CodeDom StringBuilder switchBuilder = new StringBuilder(); switchBuilder.AppendLine("switch (v) {"); foreach(var val in Enum.GetNames(sourceEnum)) { if (Enum.IsDefined(destEnum, val)) { switchBuilder.AppendFormat( "case {0}.{2}: retValue = {1}.{2}; ", sourceEnum, destEnum, val); switchBuilder.AppendLine("break;"); } } convertMethod.Statements.Add( new CodeSnippetStatement(switchBuilder));
  • 28. XML Serializer Generator Tool (Sgen.exe) The XML Serializer Generator creates an XML serialization assembly for types in a specified assembly in order to improve the startup performance of a XmlSerializer when it serializes or deserializes objects of the specified types.
  • 29. Sgen.exe rm -R *.cs sgen --assembly:Assembly-CSharp.dll --reference:UnityEngine.dll --verbose --force --keep --type:PaperPath.ObjectBaseLevelInfo --type:PaperPath.MonsterLevelInfo ... --type:PaperPath.CharacterLevelInfo result=$(find . -name *.cs) mv $result ../Assets/Classes/GeneratedSerializers.cs rm Assembly-CSharp.XmlSerializers.dll
  • 30. Do It with Unity [MenuItem("Tools/Code Generation/Serializers”)] public static void RegenerateSerializers() { System.IO.Directory.CreateDirectory(OutDir); CompileAssemblies(); PrepareSGen(); GenerateSerializers(); System.IO.Directory.Delete(OutDir, true); }
  • 31. Compile public static void CompileAssemblies() { HandleExecutable he = new HandleExecutable(); he.OutputHandler = Log; he.ErrorHandler = LogError; string Configuration = "Debug"; string Solution = "Client.sln"; he.CallExecutable("xbuild" , string.Format("/property:OutDir={0} /p:Configuration={1} {2}", OutDir, Configuration, Solution) , ””, true); }
  • 32. Prepare Script public static string GetScript() { StringBuilder sgenScript = new StringBuilder(); sgenScript.AppendLine("rm -R *.cs"); sgenScript.AppendFormat("sgen --assembly:{0} --reference:{1}” + ” --force –keep”, AssemblyPath, DependencyPaths); foreach (Type type in GetAssembly().GetTypes()) { object[] genAttributes = type.GetCustomAttributes( typeof(GenerateSerializersAttribute), false); if (genAttributes.Length > 0) sgenScript.AppendLine(" --type:" + type.FullName + " "); } sgenScript.AppendLine(); sgenScript.AppendLine("result=$(find . -name *.cs)"); sgenScript.AppendLine("mv $result ../" + GeneratedSerializersPath); sgenScript.AppendLine("rm Assembly-CSharp.XmlSerializers.dll"); return sgenScript; }
  • 33. Run Script and Postprocess public static void GenerateSerializers() { HandleExecutable he = new HandleExecutable(); he.CallExecutable("sh”, "generate.sh”, OutDir, true); string serializersCode = File.ReadAllText(GSPath); serializersCode = serializersCode.Replace( "(type.FullName)", "(type.FullName.Replace("+", "."))"); File.WriteAllText( GeneratedSerializersPath, serializersCode); }
  • 34. Thank You! Questions? Feel free to contact me ivand@appturn.com