我正在尝试构建一个T4模板,该模板将接口中的方法定义并重现签名并使用传递的参数调用基本方法.界面定义了多种方法,因此每次界面更改时重写它们都变得非常具有挑战性.另一个复杂因素是接口是具有可能的泛型方法和通用参数的通用接口.到目前为止,我能找到重现实际签名的唯一方法(对于泛型没有“1”定义)是完全重建它,这变得非常麻烦.
在我的界面中我有这样的签名:
ICar Drive<TCar>(Expression<Func<TWheel,bool>> wheels,int miles)
有没有办法用反射完全重现它,而不必分析整个MethodInfo的细节,或者有一个快速的方法将字符串拉出来,所以我可以在我的T4中写它?
任何帮助将不胜感激!
解决方法
当我需要生成代码时,我经常会查看
System.CodeDom
命名空间.它允许您构建代码的逻辑表示,然后为您构建的内容获取相应的源代码.但是,我不知道我是否可以说这种方式也不像你在答案中所说的那样“麻烦”(这肯定涉及’解剖’MethodInfo.但是,它确实给你一个相当不错的基础.通过传入要“克隆”的接口,新类的名称和要扩展的基类,如下所示:
var code = GenerateCode(typeof(TestInterface<>),"MyNewClass",typeof(TestBaseClass<>));
会导致这个:
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Runtime Version:4.0.30319.237 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace MyNamespace { using System; using System.Linq.Expressions; public class MyNewClass<TWheel> : TestInterface<TWheel>,TestBaseClass<TWheel> { public MyNamespace.ICar Drive<TCar>(Expression<Func<TWheel,int miles) { return base.Drive(wheels,miles); } } }
此外,您可以更改代码中的一些字符并切换到VB提供程序,您将获得Visual Basic输出(可能没用,但有点酷):
'------------------------------------------------------------------------------ ' <auto-generated> ' This code was generated by a tool. ' Runtime Version:4.0.30319.237 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. ' </auto-generated> '------------------------------------------------------------------------------ Option Strict Off Option Explicit On Imports System Imports System.Linq.Expressions Namespace MyNamespace Public Class MyNewClass(Of TWheel) Inherits TestInterface(Of TWheel) Implements TestBaseClass(Of TWheel) Public Function Drive(Of TCar)(ByVal wheels As Expression(Of Func(Of TWheel,Boolean)),ByVal miles As Integer) As MyNamespace.ICar Return MyBase.Drive(wheels,miles) End Function End Class End Namespace
这是GenerateCode野兽.希望这些评论可以解释发生了什么:
public static string GenerateCode(Type interfaceType,string generatedClassName,Type baseClass) { //Sanity check if (!interfaceType.IsInterface) throw new ArgumentException("Interface expected"); //I can't think of a good way to handle closed generic types so I just won't support them if (interfaceType.IsGenericType && !interfaceType.IsGenericTypeDefinition) throw new ArgumentException("Closed generic type not expected."); //Build the class var newClass = new CodeTypeDeclaration(generatedClassName) { IsClass = true,TypeAttributes = TypeAttributes.Public,BaseTypes = { //Include the interface and provided class as base classes MakeTypeReference(interfaceType),MakeTypeReference(baseClass) } }; //Add type arguments (if the interface is generic) if (interfaceType.IsGenericType) foreach (var genericArgumentType in interfaceType.GetGenericArguments()) newClass.TypeParameters.Add(genericArgumentType.Name); //Loop through each method foreach (var mi in interfaceType.GetMethods()) { //Create the method var method = new CodeMemberMethod { Attributes = MemberAttributes.Public | MemberAttributes.Final,Name = mi.Name,ReturnType = MakeTypeReference(mi.ReturnType) }; //Add any generic types if (mi.IsGenericMethod) foreach (var genericParameter in mi.GetGenericArguments()) method.TypeParameters.Add(genericParameter.Name); //Add the parameters foreach (var par in mi.GetParameters()) method.Parameters.Add(new CodeParameterDeclarationExpression(MakeTypeReference(par.ParameterType),par.Name)); //Call the same method on the base passing all the parameters var allParameters = mi.GetParameters().Select(p => new CodeArgumentReferenceExpression(p.Name)).ToArray(); var callBase = new CodeMethodInvokeExpression(new CodeBaseReferenceExpression(),mi.Name,allParameters); //If the method is void,we just call base if (mi.ReturnType == typeof(void)) method.Statements.Add(callBase); else //Otherwise,we return the value from the call to base method.Statements.Add(new CodeMethodReturnStatement(callBase)); //Add the method to our class newClass.Members.Add(method); } //TODO: Also add properties if needed? //Make a "CompileUnit" that has a namespace with some 'usings' and then // our new class. var unit = new CodeCompileUnit { Namespaces = { new CodeNamespace(interfaceType.Namespace) { Imports = { new CodeNamespaceImport("System"),new CodeNamespaceImport("System.Linq.Expressions") },Types = { newClass } } } }; //Use the C# prvider to get a code generator and generate the code //Switch this to VBCodeProvider to generate VB Code var gen = new CSharpCodeProvider().CreateGenerator(); using (var tw = new StringWriter()) { gen.GenerateCodeFromCompileUnit(unit,tw,new CodeGeneratorOptions()); return tw.ToString(); } } /// <summary> /// Helper method for expanding out a type with all it's generic types. /// It seems like there should be an easier way to do this but this work. /// </summary> private static CodeTypeReference MakeTypeReference(Type interfaceType) { //If the Type isn't generic,just wrap is directly if (!interfaceType.IsGenericType) return new CodeTypeReference(interfaceType); //Otherwise wrap it but also pass the generic arguments (recursively calling this method // on all the type arguments. return new CodeTypeReference(interfaceType.Name,interfaceType.GetGenericArguments().Select(MakeTypeReference).ToArray()); }