刚刚接触自动代码生成,便小试牛刀,解决了项目中的一些问题。
问题:我们的项目分成很多层次,当增加一个方法的时候,会显得异常繁琐,但每个层次之间的调用大同小异,所以尝试使用代码生成。现在假设有Engine, EngineBase,LocalEngine, RemoteEngine,Host等几个类,其定义和关系如下:
1 public class EngineFacade 2 { 3 private EngineBase engine = null; 4 public EngineFacade(bool isLocalEngine = true) 5 { 6 if (isLocalEngine) 7 { 8 engine = new LocalEngine(); 9 } 10 else 11 { 12 engine = new RemoteEngine(); 13 } 14 } 15 16 public bool GetCurrentStatus() 17 { 18 return true; 19 } 20 } 21 22 public abstract class EngineBase 23 { 24 public abstract bool GetCurrentStatus(); 25 } 26 27 public class LocalEngine : EngineBase 28 { 29 public override bool GetCurrentStatus() 30 { 31 return true; 32 } 33 } 34 35 public class RemoteEngine : EngineBase 36 { 37 private IHost host = null; 38 39 public RemoteEngine() 40 { 41 host = new Host(); 42 } 43 44 public override bool GetCurrentStatus() 45 { 46 return host.GetCurerntStatus(); 47 } 48 } 49 50 public interface IHost 51 { 52 bool GetCurerntStatus(); 53 } 54 55 public class Host : IHost 56 { 57 public bool GetCurerntStatus() 58 { 59 return true; 60 } 61 }
上诉定义的类会被SampleClient调用:
public class SampleClient { public void Test() { var engine = new EngineFacade(false); Console.WriteLine(engine.GetCurrentStatus()); } }
如果我们需要增加一个新的方法SetStatus,如何避免额外的体力劳动呢?
解决方案:使用CodeDom进行动态代码生成。
1 class CodeGenerator 2 { 3 private CodeCompileUnit targetUnit = new CodeCompileUnit(); 4 private CodeTypeDeclaration targetClass = new CodeTypeDeclaration("Test"); 5 private static readonly string targetInstance = "engineInstance"; 6 7 public CodeGenerator() 8 { 9 CodeNamespace sample = new CodeNamespace("CodeDomTest"); 10 sample.Imports.Add(new CodeNamespaceImport("System")); 11 targetClass.IsClass = true; 12 targetClass.TypeAttributes = TypeAttributes.Public; 13 sample.Types.Add(targetClass); 14 targetUnit.Namespaces.Add(sample); 15 } 16 17 public void AddMethod(string name, Type returnType, string comments, CodeExpression codeExpression) 18 { 19 CodeMemberMethod method = new CodeMemberMethod(); 20 method.Attributes = MemberAttributes.Public | MemberAttributes.Override; 21 method.Name = name; 22 method.ReturnType = new CodeTypeReference(returnType); 23 method.Comments.Add(new CodeCommentStatement(comments)); 24 CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(); 25 returnStatement.Expression = codeExpression; 26 method.Statements.Add(returnStatement); 27 targetClass.Members.Add(method); 28 } 29 30 public void AddMethod(string name, Type returnType, string comments, CodeExpression codeExpression, string paraName, Type paraType) 31 { 32 CodeMemberMethod method = new CodeMemberMethod(); 33 method.Attributes = MemberAttributes.Public | MemberAttributes.Override; 34 method.Name = name; 35 method.ReturnType = new CodeTypeReference(returnType); 36 method.Comments.Add(new CodeCommentStatement(comments)); 37 method.Parameters.Add(new CodeParameterDeclarationExpression(paraType, paraName)); 38 CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(); 39 returnStatement.Expression = codeExpression; 40 method.Statements.Add(returnStatement); 41 targetClass.Members.Add(method); 42 } 43 44 public void AddMethod(string name, Type returnType, string comments, CodeExpression codeExpression, List<CodeParameterDeclarationExpression> parameters) 45 { 46 CodeMemberMethod method = new CodeMemberMethod(); 47 method.Attributes = MemberAttributes.Public | MemberAttributes.Override; 48 method.Name = name; 49 method.ReturnType = new CodeTypeReference(returnType); 50 method.Comments.Add(new CodeCommentStatement(comments)); 51 method.Parameters.AddRange(parameters.ToArray()); 52 CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(); 53 returnStatement.Expression = codeExpression; 54 method.Statements.Add(returnStatement); 55 targetClass.Members.Add(method); 56 } 57 58 59 public void AddAbstractMethod(string name, Type returnType, string comments) 60 { 61 CodeMemberMethod method = new CodeMemberMethod(); 62 method.Attributes = MemberAttributes.Abstract | MemberAttributes.Public; 63 method.Name = name; 64 method.ReturnType = new CodeTypeReference(returnType); 65 method.Comments.Add(new CodeCommentStatement(comments)); 66 targetClass.Members.Add(method); 67 } 68 69 public void AddAbstractMethod(string name, Type returnType, string comments, string paraName, Type paraType) 70 { 71 CodeMemberMethod method = new CodeMemberMethod(); 72 method.Attributes = MemberAttributes.Abstract | MemberAttributes.Public; 73 method.Name = name; 74 method.ReturnType = new CodeTypeReference(returnType); 75 method.Parameters.Add(new CodeParameterDeclarationExpression(paraType, paraName)); 76 method.Comments.Add(new CodeCommentStatement(comments)); 77 targetClass.Members.Add(method); 78 } 79 80 public void AddField(string name, Type fieldType) 81 { 82 CodeMemberField member = new CodeMemberField(); 83 member.Attributes = MemberAttributes.Private; 84 member.Name = name; 85 member.Type = new CodeTypeReference(fieldType); 86 targetClass.Members.Add(member); 87 } 88 89 public void GenerateCode() 90 { 91 CodeDomProvider provider = new CSharpCodeProvider(); 92 CodeGeneratorOptions options = new CodeGeneratorOptions(); 93 options.BracingStyle = "C"; 94 using (StreamWriter streamWriter = new StreamWriter("SampleReactorCode.cs")) 95 { 96 provider.GenerateCodeFromCompileUnit(targetUnit, streamWriter, options); 97 } 98 } 99 100 public static void Start() 101 { 102 CodeGenerator sample = new CodeGenerator(); 103 Test(sample); 104 sample.GenerateCode(); 105 } 106 107 private static void Test(CodeGenerator sample) 108 { 109 var methodToAdd = "Test"; 110 var returnType = typeof(Dictionary<string, object>); 111 var paraname = "key"; 112 var paraType = typeof(string); 113 var parTypes = new Dictionary<string, Type>() { { "no", typeof(int) }, { paraname, paraType } }; 114 115 var parameters = GenerateParameters(parTypes); 116 var parArguments = GenerateParametersArguments(parTypes).ToArray(); 117 118 sample.AddMethod(methodToAdd, returnType, "This is for engine facade", 119 new CodeMethodInvokeExpression( 120 new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), targetInstance), 121 methodToAdd, parArguments), parameters); 122 sample.AddAbstractMethod(methodToAdd, returnType, "This is for engine base", paraname, paraType); 123 sample.AddMethod(methodToAdd, returnType, "This is for local engine", 124 new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "scenario"), 125 methodToAdd, parArguments), parameters); 126 sample.AddMethod(methodToAdd, returnType, "This is for remote engine", 127 new CodeMethodInvokeExpression( 128 new CodeTypeReferenceExpression(new CodeTypeReference("HostProxy")), methodToAdd, 129 parArguments), parameters); 130 131 var dump = parArguments.ToList(); 132 parameters.Insert(0, new CodeParameterDeclarationExpression(typeof(string), "engineKey")); 133 dump.Insert(0, new CodeArgumentReferenceExpression("engineKey")); 134 parArguments = dump.ToArray(); 135 136 sample.AddMethod(methodToAdd, returnType, "This is for host", 137 new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("InstanceManager"), methodToAdd, parArguments), 138 parameters); 139 140 dump.Insert(0, new CodePrimitiveExpression(methodToAdd)); 141 dump.Insert(0, new CodeArgumentReferenceExpression("host_name")); 142 parArguments = dump.ToArray(); 143 sample.AddMethod(methodToAdd, returnType, "This is for host", 144 new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("host"), string.Format("Invoke<{0}>", returnType.ToString()) 145 , parArguments), parameters); 146 } 147 148 private static List<CodeParameterDeclarationExpression> GenerateParameters(Dictionary<string, Type> parameterTypes) 149 { 150 return parameterTypes.Select(parameterType => new CodeParameterDeclarationExpression(parameterType.Value, parameterType.Key)).ToList(); 151 } 152 private static List<CodeExpression> GenerateParametersArguments(Dictionary<string, Type> parameterTypes) 153 { 154 return parameterTypes.Select(parameterType => (CodeExpression)new CodeArgumentReferenceExpression(parameterType.Key)).ToList(); 155 } 156 }
如何使用CodeDom,请参考动态源代码生成和编译。