Подпись фактического метода с использованием отражения - PullRequest
6 голосов
/ 15 сентября 2011

Я пытаюсь создать шаблон T4, который будет принимать определения методов в интерфейсе, воспроизводить сигнатуру и вызывать базовый метод с переданными параметрами. Интерфейс определяет множество методов, поэтому переписывать их каждый раз, когда интерфейс меняется, становится очень сложно. Еще одним усложнением интерфейса является универсальный интерфейс с возможными универсальными методами и общими параметрами. Пока что единственный способ воспроизвести действительную подпись (без определения «1» для дженериков) - это полностью перестроить ее, что становится очень громоздким.

В случае, если у меня в интерфейсе есть такая подпись:

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

Есть ли какой-нибудь способ полностью воспроизвести это с помощью отражения, не выявляя все детали MethodInfo, или есть быстрый способ вывести строку выше, чтобы я мог записать ее в свой T4?

Любая помощь будет принята с благодарностью!

1 Ответ

8 голосов
/ 17 сентября 2011

Когда мне нужно сгенерировать код, я часто обращаюсь к пространству имен 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());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...