Using Reflections and Automatic Code Generation
- 2. It is Very Technical:
• A lot of code
• Only 5 pictures
• About not writing code
- 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
- 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);
}
- 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);
}