Внедрение кода во время выполнения с использованием DynamicMethod?

Рассмотрим следующий тривиальный код:

using System;   
class Test
{
    delegate int FooDelegate(int i);
    FooDelegate Foo = FooImplementation;
    static int FooImplementation(int i)
    {
        return i + 1;
    }

    public static void Main()
    {
         Foo(1);
    }
}

То, что я хотел бы сделать, это ввести некоторый код отладки в делегат Foo, что было бы эквивалентно:

FooDelegate Foo = delegate(int i)
{
    try
    {
        DebugPrologue();
        return FooImplementation(i);
    }
    finally
    {
        DebugEpilogue();
    }
};

Суть в том, что я должен иметь возможность делать это во время выполнения, поэтому о методах времени компиляции и постобработки не может быть и речи.

Мой первоначальный подход использовал Delegate.Combine() для добавления методов пролога и эпилога к делегату Foo. Увы, это не сработает, так как искажает возвращаемые значения.

Моя текущая идея состоит в том, чтобы использовать System.Reflection.Emit и DynamicMethod в качестве потенциального решения. Насколько я могу судить, мне нужно получить MethodInfo для FooImplementation, получить его MethodBody, преобразовать его в DynamicMethod и внедрить в него блок try-finally.

К сожалению, я совершенно не представляю, как это сделать. Есть желающие протянуть руку помощи? Или у вас есть другая (желательно более простая) идея?

Изменить: здесь используется отладка привязки OpenGL (http://www.opentk.com). Нам нужно внедрить 2226 методов с совершенно разными параметрами, поэтому необходим общий подход.


person The Fiddler    schedule 04.12.2010    source источник


Ответы (6)


Вы можете использовать выражения.

var param = Expression.Parameter(typeof(int), "i");

Foo = Expression.Lambda(
         Expression.TryFinally(
            Expression.Block(
               <expression for DebugPrologue>,
               Expression.Invoke(<expression for FooImplementation>, param)),
            <expression for DebugEpilogue>),
         param)
      .Compile();

Таким образом, вы можете создавать выражения для пролога и эпилога во время выполнения.

person configurator    schedule 05.12.2010
comment
Спасибо, очень просто и элегантно. Позвольте мне протестировать это решение, я думаю, оно должно работать! - person The Fiddler; 05.12.2010
comment
К вашему сведению, скомпилированные выражения используют DynamicMethod за кулисами. - person Anton Tykhyy; 03.09.2012

Обратите внимание, что вы пытаетесь сделать, но если это просто перенаправление FooImplementation, на которое указывает поле Foo, вы можете просто сделать:

class Test
{
    delegate int FooDelegate(int i);
    static FooDelegate Foo = FooImplementation;

    static int FooImplementation(int i)
    {
        return i + 1;
    }

    public static void Main()
    {
        Inject();
        Foo(1);
    }


    public static void Inject()
    {
        var oldImpl = Foo;

        Foo = i =>
            {
                try
                {
                    BeginDebug();
                    return oldImpl(i);
                }
                finally
                {
                    EndDebug();
                }
            };
    }

    public static void BeginDebug()
    {
        Console.WriteLine("Begin Foo");
    }

    public static void EndDebug()
    {
        Console.WriteLine("End Foo");
    }
}

Конечно, Inject не обязательно должен быть в том же классе, однако, если поле/свойство не является общедоступным, вам придется использовать Reflection, чтобы сделать это, хотя это не так сложно.

person Doggett    schedule 04.12.2010
comment
Это решение отлично работает для приемлемого количества методов инъекции. К сожалению, в этом конкретном случае мне нужно ввести 2226 методов с совершенно разными параметрами (это привязка OpenGL, я обновлю исходный вопрос, чтобы отразить это). - person The Fiddler; 05.12.2010

Рассматривали ли вы возможность использования чего-то вроде: Postsharp? http://www.sharpcrafters.com/postsharp

или внедрение политики корпоративной библиотеки? http://msdn.microsoft.com/en-us/library/ff650672.aspx

или даже Mono.Cecil? http://www.mono-project.com/Cecil

person Doobi    schedule 04.12.2010
comment
Mono.Cecil и postsharp запускаются во время компиляции, что здесь не сработает (мне нужно иметь возможность отключать/включать это во время выполнения). Я раньше не видел Enterprise Library, спасибо, посмотрю. - person The Fiddler; 05.12.2010

TypeMock также может быть жизнеспособным решением вашей проблемы: http://www.typemock.com/

Вы также можете использовать класс RealProxy (http://msdn.microsoft.com/en-us/library/system.runtime.remoting.proxies.realproxy.aspx) для создания собственного прокси-сервера во время выполнения, который сначала вызовет введенный код. а затем вызовите фактический метод.

person SpeksETC    schedule 04.12.2010

Вы также можете попробовать CInject на codeplex, чтобы очень легко вставлять код в управляемый код (C# или VB.NET).

person GeekzSG    schedule 03.09.2012

В итоге я реализовал свое собственное решение по переписыванию с помощью Mono.Cecil. Просто, быстро и работает нормально. К сожалению, это должно быть сделано как событие после сборки, поэтому на самом деле это не внедрение кода во время выполнения.

person The Fiddler    schedule 24.12.2013