【问题标题】:Actual Method Signature Using Reflection使用反射的实际方法签名
【发布时间】:2011-09-15 18:17:04
【问题描述】:

我正在尝试构建一个 T4 模板,该模板将采用接口中的方法定义并重现签名并使用传递的参数调用基本方法。接口定义了许多方法,因此每次接口更改时重写它们变得非常具有挑战性。另一个复杂之处是该接口是具有可能的泛型方法和泛型参数的泛型接口。到目前为止,我能找到重现实际签名(没有泛型的“`1”定义)的唯一方法是完全重建它,这变得非常麻烦。

如果我的界面中有这样的签名:

ICar Drive<TCar>(Expression<Func<TWheel, bool>> wheels, int miles)

有没有什么方法可以通过反射完全重现它,而不必剖析整个 MethodInfo 细节,或者有没有一种快速的方法来获取上面的字符串以便我可以将它写到我的 T4 中?

任何帮助将不胜感激!

【问题讨论】:

  • 不是您问题的答案,但根据我的经验,在您的解决方案中混合 T4 和对程序集的反思会以泪水告终。 VS(充当 T4 处理器)会将程序集加载到内存中以进行反映,然后在下次构建它们时会出现“文件正在使用”错误。建议:另辟蹊径。
  • 可以访问接口的源代码吗?从文本中提取信息可能比使用反射重构源更容易。
  • .NET Framework 中不包含用于生成方法签名的方法的一个原因是它必须支持多种语言,因为可以从 C#、VB.NET、J# 调用 .NET 程序集、JScript、PowerShell 等,它们的方法签名都有不同的语法。

标签: c# generics reflection .net-4.0


【解决方案1】:

当我需要生成代码时,我经常查看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 野兽。希望 cmets 可以解释发生了什么:

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());
}

【讨论】:

  • 非常彻底的答案。谢谢!
猜你喜欢
  • 2022-09-29
  • 1970-01-01
  • 2015-06-07
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 2010-10-03
  • 1970-01-01
相关资源
最近更新 更多