使用反射的实际方法签名



我正在尝试构建一个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, bool>> wheels, 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());
}

相关内容

  • 没有找到相关文章

最新更新