Динамічно замінювати вміст методу C #?


108

Що я хочу зробити - це змінити спосіб виконання методу C # при його виклику, щоб я міг написати щось подібне:

[Distributed]
public DTask<bool> Solve(int n, DEvent<bool> callback)
{
    for (int m = 2; m < n - 1; m += 1)
        if (m % n == 0)
            return false;
    return true;
}

Під час виконання роботи мені потрібно мати можливість аналізувати методи, які мають атрибут Distributed (що я вже можу зробити), а потім вставляти код до того, як тіло функції виконуватиметься і після повернення функції. Що ще важливіше, мені потрібно вміти це робити, не змінюючи код, де викликається Solve або на початку функції (під час компіляції; це робиться в час виконання).

На даний момент я спробував цей біт коду (припустимо, t - тип, в якому зберігається Solve, а m - MethodInfo Solve) :

private void WrapMethod(Type t, MethodInfo m)
{
    // Generate ILasm for delegate.
    byte[] il = typeof(Dpm).GetMethod("ReplacedSolve").GetMethodBody().GetILAsByteArray();

    // Pin the bytes in the garbage collection.
    GCHandle h = GCHandle.Alloc((object)il, GCHandleType.Pinned);
    IntPtr addr = h.AddrOfPinnedObject();
    int size = il.Length;

    // Swap the method.
    MethodRental.SwapMethodBody(t, m.MetadataToken, addr, size, MethodRental.JitImmediate);
}

public DTask<bool> ReplacedSolve(int n, DEvent<bool> callback)
{
    Console.WriteLine("This was executed instead!");
    return true;
}

Однак MethodRental.SwapMethodBody працює лише на динамічних модулях; не ті, які вже були складені та збережені у складі.

Тому я шукаю спосіб ефективного виконання SwapMethodBody за методом, який вже зберігається у завантаженій і виконуючій збірці .

Зауважте, це не проблема, якщо мені доведеться повністю скопіювати метод в динамічний модуль, але в цьому випадку мені потрібно знайти спосіб копіювання через ІЛ, а також оновити всі виклики Solve () таким чином, щоб вони вказував би на нову копію.


3
Неможливо замінити вже завантажені методи. В іншому випадку Spring.Net не повинен був би робити дивні речі з проксі-серверами та інтерфейсами :-) Прочитайте це питання, це дотично до вашої проблеми: stackoverflow.com/questions/25803/… (якщо ви можете перехопити це, ви можете щось подібне -замініть його ... Якщо ви не можете 1, то явно ви не можете 2).
xanatos

У такому випадку, чи є спосіб скопіювати метод у динамічний модуль та оновити решту збірки таким чином, щоб виклики цього методу вказували на нову копію?
Червень Родос

Той же старий-такий же старий. Якби це можна було зробити легко, всі різні контейнери IoC, ймовірно, зробили б це. Вони цього не роблять -> 99%, цього не можна зробити :-) (без страшних і непристойних злому). Є одна надія: вони обіцяли метапрограмування та асинхронізацію в C # 5.0. Асинхрон ми бачили ... Метапрограмування нічого ... АЛЕ це могло бути!
xanatos

1
Ви дійсно не пояснили, чому хочете пустити себе в щось таке болісне.
DanielOfTaebl

6
Будь ласка, дивіться мою відповідь нижче. Це цілком можливо. У коді, яким ви не володієте, і під час виконання. Я не розумію, чому так багато хто думає, що це неможливо.
Андреас Пардейке

Відповіді:


201

Розкриття: Гармонія - це бібліотека, яка написана та підтримується мною, автором цієї публікації.

Harmony 2 - це бібліотека з відкритим кодом (ліцензія MIT), призначена для заміни, декорування чи модифікації існуючих методів будь-якого типу C # під час виконання. Основна увага - ігри та плагіни, написані на Mono або .NET. Він дбає про багаторазові зміни одного і того ж методу - вони накопичуються замість того, щоб перезаписати один одного.

Він створює динамічні методи заміни для кожного оригінального методу та висилає їм код, який викликає власні методи на початку та в кінці. Це також дозволяє писати фільтри для обробки оригінального коду IL та спеціальних обробників виключень, що дозволяє більш детально керувати оригінальним методом.

Щоб завершити процес, він пише простий стрибок асемблера в батут оригінального методу, який вказує на асемблер, сформований при складанні динамічного методу. Це працює для 32 / 64Bit для Windows, macOS та будь-якого Linux, який підтримує Mono.

Документацію можна знайти тут .

Приклад

( Джерело )

Оригінальний код

public class SomeGameClass
{
    private bool isRunning;
    private int counter;

    private int DoSomething()
    {
        if (isRunning)
        {
            counter++;
            return counter * 10;
        }
    }
}

Обшивка анотаціями Harmony

using SomeGame;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");
        harmony.PatchAll();
    }
}

[HarmonyPatch(typeof(SomeGameClass))]
[HarmonyPatch("DoSomething")]
class Patch01
{
    static FieldRef<SomeGameClass,bool> isRunningRef =
        AccessTools.FieldRefAccess<SomeGameClass, bool>("isRunning");

    static bool Prefix(SomeGameClass __instance, ref int ___counter)
    {
        isRunningRef(__instance) = true;
        if (___counter > 100)
            return false;
        ___counter = 0;
        return true;
    }

    static void Postfix(ref int __result)
    {
        __result *= 2;
    }
}

Як варіант, ручне виправлення з відображенням

using SomeGame;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");

        var mOriginal = typeof(SomeGameClass).GetMethod("DoSomething", BindingFlags.Instance | BindingFlags.NonPublic);
        var mPrefix = typeof(MyPatcher).GetMethod("MyPrefix", BindingFlags.Static | BindingFlags.Public);
        var mPostfix = typeof(MyPatcher).GetMethod("MyPostfix", BindingFlags.Static | BindingFlags.Public);
        // add null checks here

        harmony.Patch(mOriginal, new HarmonyMethod(mPrefix), new HarmonyMethod(mPostfix));
    }

    public static void MyPrefix()
    {
        // ...
    }

    public static void MyPostfix()
    {
        // ...
    }
}

Подивився на вихідний код, дуже цікаво! Чи можете ви пояснити (тут та / або в документації), як працюють конкретні інструкції, які використовуються для виконання стрибка (в Memory.WriteJump)?
Том

Щоб частково відповісти на мій власний коментар: 48 B8 <QWord>переміщує миттєве значення QWord на rax, тоді FF E0є jmp rax- все зрозуміло! Залишилося моє запитання про E9 <DWord>випадок (близький стрибок): здається, в цьому випадку стрибок у близькому напрямку зберігається і модифікація знаходиться в цілі стрибка; коли Mono генерує такий код в першу чергу і чому він отримує такий спеціальний режим?
Том

1
Наскільки я можу сказати, він ще не підтримує .NET Core 2, отримуючи деякі винятки з AppDomain.CurrentDomain.DefineDynamicAssembly
Макс

1
Мій друг 0x0ade згадав мені, що існує менш зріла альтернатива, яка працює на .NET Core, а саме MonoMod.RuntimeDetour на NuGet.
Андреас Пардейке

1
Оновлення: включивши посилання на System.Reflection.Emit, Harmony тепер компілює і тестує ОК з .NET Core 3
Andreas Pardeike

181

Для .NET 4 і вище

using System;
using System.Reflection;
using System.Runtime.CompilerServices;


namespace InjectionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Target targetInstance = new Target();

            targetInstance.test();

            Injection.install(1);
            Injection.install(2);
            Injection.install(3);
            Injection.install(4);

            targetInstance.test();

            Console.Read();
        }
    }

    public class Target
    {
        public void test()
        {
            targetMethod1();
            Console.WriteLine(targetMethod2());
            targetMethod3("Test");
            targetMethod4();
        }

        private void targetMethod1()
        {
            Console.WriteLine("Target.targetMethod1()");

        }

        private string targetMethod2()
        {
            Console.WriteLine("Target.targetMethod2()");
            return "Not injected 2";
        }

        public void targetMethod3(string text)
        {
            Console.WriteLine("Target.targetMethod3("+text+")");
        }

        private void targetMethod4()
        {
            Console.WriteLine("Target.targetMethod4()");
        }
    }

    public class Injection
    {        
        public static void install(int funcNum)
        {
            MethodInfo methodToReplace = typeof(Target).GetMethod("targetMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            MethodInfo methodToInject = typeof(Injection).GetMethod("injectionMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);

            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;
#if DEBUG
                    Console.WriteLine("\nVersion x86 Debug\n");

                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                    Console.WriteLine("\nVersion x86 Release\n");
                    *tar = *inj;
#endif
                }
                else
                {

                    long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer()+1;
                    long* tar = (long*)methodToReplace.MethodHandle.Value.ToPointer()+1;
#if DEBUG
                    Console.WriteLine("\nVersion x64 Debug\n");
                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;


                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                    Console.WriteLine("\nVersion x64 Release\n");
                    *tar = *inj;
#endif
                }
            }
        }

        private void injectionMethod1()
        {
            Console.WriteLine("Injection.injectionMethod1");
        }

        private string injectionMethod2()
        {
            Console.WriteLine("Injection.injectionMethod2");
            return "Injected 2";
        }

        private void injectionMethod3(string text)
        {
            Console.WriteLine("Injection.injectionMethod3 " + text);
        }

        private void injectionMethod4()
        {
            System.Diagnostics.Process.Start("calc");
        }
    }

}

14
Це заслуговує на стільки нових результатів. У мене зовсім інший сценарій, але цей фрагмент - саме те, що мені потрібно було, щоб настроїти мене в правильному напрямку. Дякую.
SC

2
@Logman чудова відповідь. Але моє питання: Що відбувається в режимі налагодження? І чи можна замінити лише одну інструкцію? Наприклад, якщо я хочу замінити умовний стрибок на безумовний? AFAIK ви замінюєте скомпільований метод, тому визначити, яку умову нам слід замінити непросто ...
Олексій Жуковський

2
@AlexZhukovskiy, якщо вам подобається розмістити його на стеці та надішліть мені посилання. Я загляну в це і дам вам відповідь після вихідних. Машина Я також розгляну ваше питання після вихідних.
Логман

2
Дві речі, які я помітив, роблячи це для тесту на інтеграцію з MSTest: (1) Коли ви використовуєте thisвсередині injectionMethod*()нього, він посилається на Injectionекземпляр під час компіляції , але на Targetекземпляр під час виконання (це справедливо для всіх посилань на членів екземпляра, які ви використовуєте всередині введеного метод). (2) Чомусь #DEBUGчастина працювала лише під час налагодження тесту, але не під час запуску тесту, який був налагоджений налагодженням. Я в кінцевому підсумку завжди використовував #elseчастину. Я не розумію, чому це працює, але це так.
Доброї ночі Nerd Pride

2
дуже хороша. час все зламати! @GoodNightNerdPride використовувати Debugger.IsAttachedзамість #if препроцесора
M.kazem Akhgary

25

МОЖЕТЕ змінити вміст методу під час виконання. Але вам цього не слід, і настійно рекомендується зберігати це в тестових цілях.

Просто подивіться на:

http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time

В основному, ви можете:

  1. Отримайте вміст методу IL через MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Возитися з цими байтами.

    Якщо ви просто хочете додати або додати якийсь код, тоді просто попередьте / додайте потрібні вам коди (будьте обережні, залишаючи стек в чистоті)

    Ось декілька порад щодо "некомпіляції" існуючих ІЛ:

    • Повернені байти - це послідовність інструкцій IL, а потім їх аргументи (якщо такі є, наприклад, ".call" має один аргумент: токен методу, що називається, а ".pop" не має)
    • Відповідність між кодами IL та байтами, які ви знайдете у поверненому масиві, можна знайти за допомогою OpCodes.YourOpCode.Value (що є реальним значенням байта коду опкоду, збереженим у вашій збірці)
    • Аргументи, додані після IL-кодів, можуть мати різний розмір (від одного до кількох байтів), залежно від названого коду
    • Ви можете знайти лексеми, на які посилаються тези аргументів за допомогою відповідних методів. Наприклад, якщо ваш IL містить ".call 354354" (кодується як 28 00 05 68 32 в шестикутнику, 28h = 40 є ".call" опкодом і 56832h = 354354), відповідний названий метод можна знайти за допомогою MethodBase.GetMethodFromHandle (354354 )
  3. Після модифікації, байтовий масив IL можна повторно вводити через InjectionHelper.UpdateILCodes (метод MethodInfo, байт [] ilCodes) - див. Посилання, згадане вище

    Це "небезпечна" частина ... Це добре працює, але це полягає в злому внутрішніх механізмів CLR ...


7
Щоб бути педантичним, 354354 (0x00056832) не є дійсним маркером метаданих, байт високого порядку повинен бути 0x06 (MethodDef), 0x0A (MemberRef) або 0x2B (MethodSpec). Крім того, маркер метаданих повинен бути записаний у порядку малобайкових байтів. Нарешті, маркер метаданих є специфічним для модуля, і MethodInfo.MetadataToken поверне маркер з модуля декларування, зробивши його непридатним, якщо ви хочете викликати метод, не визначений у тому ж модулі, що і метод, який ви змінюєте.
Брайан Райхле

13

ви можете замінити його, якщо метод не віртуальний, не загальний, не в загальному типі, не накреслений і на x86 формі форми:

MethodInfo methodToReplace = ...
RuntimeHelpers.PrepareMetod(methodToReplace.MethodHandle);

var getDynamicHandle = Delegate.CreateDelegate(Metadata<Func<DynamicMethod, RuntimeMethodHandle>>.Type, Metadata<DynamicMethod>.Type.GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic)) as Func<DynamicMethod, RuntimeMethodHandle>;

var newMethod = new DynamicMethod(...);
var body = newMethod.GetILGenerator();
body.Emit(...) // do what you want.
body.Emit(OpCodes.jmp, methodToReplace);
body.Emit(OpCodes.ret);

var handle = getDynamicHandle(newMethod);
RuntimeHelpers.PrepareMethod(handle);

*((int*)new IntPtr(((int*)methodToReplace.MethodHandle.Value.ToPointer() + 2)).ToPointer()) = handle.GetFunctionPointer().ToInt32();

//all call on methodToReplace redirect to newMethod and methodToReplace is called in newMethod and you can continue to debug it, enjoy.

Це виглядає божевільно небезпечно. Я дуже сподіваюся, що ніхто не використовує це у виробничому коді.
Брайан Райхле

2
Таке це використовується інструментами моніторингу продуктивності додатків (APM), а також використовується у виробництві.
Мартін Керстен

1
Дякую за відповідь, я працюю над проектом, щоб запропонувати такі можливості як API орієнтованого на аспекти. Я вирішив своє обмеження щодо управління віртуальним методом та загальним методом як на x86, так і на x64. Повідомте мене, якщо вам потрібно більше деталей.
Teter28

6
Що таке клас метаданих?
Себастьян

Ця відповідь є псевдокодом і застаріла. Багато методів більше не існують.
N-ate

9

Існує пара рамок, що дозволяє динамічно змінювати будь-який метод під час виконання (вони використовують інтерфейс ICLRProfiling, згаданий користувачем152949):

  • Prig : Безкоштовно та з відкритим кодом!
  • Microsoft Fakes : комерційний, включений до Visual Studio Premium та Ultimate, але не для спільноти та професіонала
  • Telerik JustMock : Комерційна, доступна " легка " версія
  • Ізолятор Typemock : комерційний

Існує також декілька фреймворків, які знущаються з внутрішніх .NET. Вони, ймовірно, більш крихкі, і, ймовірно, не можуть змінити вбудований код, але, з іншого боку, вони повністю автономні і не вимагають від вас використання спеціальні пускові установки.

  • Гармонія : ліцензія на MIT. Здається, насправді успішно використовувались у кількох ігрових модах, які підтримують і .NET, і Mono.
  • Deviare у процесорі приладобудування : GPLv3 та комерційний. Підтримка .NET на даний момент позначена як експериментальна, але, з іншого боку, має перевагу комерційної підтримки.

8

Рішення Logman , але з інтерфейсом для заміни тіл методів. Також простіший приклад.

using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace DynamicMojo
{
    class Program
    {
        static void Main(string[] args)
        {
            Animal kitty = new HouseCat();
            Animal lion = new Lion();
            var meow = typeof(HouseCat).GetMethod("Meow", BindingFlags.Instance | BindingFlags.NonPublic);
            var roar = typeof(Lion).GetMethod("Roar", BindingFlags.Instance | BindingFlags.NonPublic);

            Console.WriteLine("<==(Normal Run)==>");
            kitty.MakeNoise(); //HouseCat: Meow.
            lion.MakeNoise(); //Lion: Roar!

            Console.WriteLine("<==(Dynamic Mojo!)==>");
            DynamicMojo.SwapMethodBodies(meow, roar);
            kitty.MakeNoise(); //HouseCat: Roar!
            lion.MakeNoise(); //Lion: Meow.

            Console.WriteLine("<==(Normality Restored)==>");
            DynamicMojo.SwapMethodBodies(meow, roar);
            kitty.MakeNoise(); //HouseCat: Meow.
            lion.MakeNoise(); //Lion: Roar!

            Console.Read();
        }
    }

    public abstract class Animal
    {
        public void MakeNoise() => Console.WriteLine($"{this.GetType().Name}: {GetSound()}");

        protected abstract string GetSound();
    }

    public sealed class HouseCat : Animal
    {
        protected override string GetSound() => Meow();

        private string Meow() => "Meow.";
    }

    public sealed class Lion : Animal
    {
        protected override string GetSound() => Roar();

        private string Roar() => "Roar!";
    }

    public static class DynamicMojo
    {
        /// <summary>
        /// Swaps the function pointers for a and b, effectively swapping the method bodies.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// a and b must have same signature
        /// </exception>
        /// <param name="a">Method to swap</param>
        /// <param name="b">Method to swap</param>
        public static void SwapMethodBodies(MethodInfo a, MethodInfo b)
        {
            if (!HasSameSignature(a, b))
            {
                throw new ArgumentException("a and b must have have same signature");
            }

            RuntimeHelpers.PrepareMethod(a.MethodHandle);
            RuntimeHelpers.PrepareMethod(b.MethodHandle);

            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*)b.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*)a.MethodHandle.Value.ToPointer() + 2;

                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    int tmp = *tarSrc;
                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
                    *injSrc = (((int)tarInst + 5) + tmp) - ((int)injInst + 5);
                }
                else
                {
                    throw new NotImplementedException($"{nameof(SwapMethodBodies)} doesn't yet handle IntPtr size of {IntPtr.Size}");
                }
            }
        }

        private static bool HasSameSignature(MethodInfo a, MethodInfo b)
        {
            bool sameParams = !a.GetParameters().Any(x => !b.GetParameters().Any(y => x == y));
            bool sameReturnType = a.ReturnType == b.ReturnType;
            return sameParams && sameReturnType;
        }
    }
}

1
Це дало мені: Виняток типу "System.AccessViolationException" стався в MA.ELCalc.FunctionalTests.dll, але він не оброблявся в коді користувача Додаткова інформація: Спроба читати чи записувати захищену пам'ять. Це часто свідчить про пошкодження іншої пам’яті. ,,, Під час заміни геттера.
N-ate

У мене виняток "wapMethodBodies ще не обробляє IntPtr розміром 8"
Phong Dao

6

Виходячи з відповіді на це питання та іншого, я придумав цю прибрану версію:

// Note: This method replaces methodToReplace with methodToInject
// Note: methodToInject will still remain pointing to the same location
public static unsafe MethodReplacementState Replace(this MethodInfo methodToReplace, MethodInfo methodToInject)
        {
//#if DEBUG
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
//#endif
            MethodReplacementState state;

            IntPtr tar = methodToReplace.MethodHandle.Value;
            if (!methodToReplace.IsVirtual)
                tar += 8;
            else
            {
                var index = (int)(((*(long*)tar) >> 32) & 0xFF);
                var classStart = *(IntPtr*)(methodToReplace.DeclaringType.TypeHandle.Value + (IntPtr.Size == 4 ? 40 : 64));
                tar = classStart + IntPtr.Size * index;
            }
            var inj = methodToInject.MethodHandle.Value + 8;
#if DEBUG
            tar = *(IntPtr*)tar + 1;
            inj = *(IntPtr*)inj + 1;
            state.Location = tar;
            state.OriginalValue = new IntPtr(*(int*)tar);

            *(int*)tar = *(int*)inj + (int)(long)inj - (int)(long)tar;
            return state;

#else
            state.Location = tar;
            state.OriginalValue = *(IntPtr*)tar;
            * (IntPtr*)tar = *(IntPtr*)inj;
            return state;
#endif
        }
    }

    public struct MethodReplacementState : IDisposable
    {
        internal IntPtr Location;
        internal IntPtr OriginalValue;
        public void Dispose()
        {
            this.Restore();
        }

        public unsafe void Restore()
        {
#if DEBUG
            *(int*)Location = (int)OriginalValue;
#else
            *(IntPtr*)Location = OriginalValue;
#endif
        }
    }

На даний момент найкраща відповідь
Євген Горбовий

було б корисно додати приклад використання
kofifus


3

Я знаю, що це не точна відповідь на ваше запитання, але звичайний спосіб зробити це за допомогою фабрик / проксі-підходу.

Спочатку ми оголошуємо базовий тип.

public class SimpleClass
{
    public virtual DTask<bool> Solve(int n, DEvent<bool> callback)
    {
        for (int m = 2; m < n - 1; m += 1)
            if (m % n == 0)
                return false;
        return true;
    }
}

Тоді ми можемо оголосити похідний тип (назвати його проксі).

public class DistributedClass
{
    public override DTask<bool> Solve(int n, DEvent<bool> callback)
    {
        CodeToExecuteBefore();
        return base.Slove(n, callback);
    }
}

// At runtime

MyClass myInstance;

if (distributed)
    myInstance = new DistributedClass();
else
    myInstance = new SimpleClass();

Похідний тип можна також генерувати під час виконання.

public static class Distributeds
{
    private static readonly ConcurrentDictionary<Type, Type> pDistributedTypes = new ConcurrentDictionary<Type, Type>();

    public Type MakeDistributedType(Type type)
    {
        Type result;
        if (!pDistributedTypes.TryGetValue(type, out result))
        {
            if (there is at least one method that have [Distributed] attribute)
            {
                result = create a new dynamic type that inherits the specified type;
            }
            else
            {
                result = type;
            }

            pDistributedTypes[type] = result;
        }
        return result;
    }

    public T MakeDistributedInstance<T>()
        where T : class
    {
        Type type = MakeDistributedType(typeof(T));
        if (type != null)
        {
            // Instead of activator you can also register a constructor delegate generated at runtime if performances are important.
            return Activator.CreateInstance(type);
        }
        return null;
    }
}

// In your code...

MyClass myclass = Distributeds.MakeDistributedInstance<MyClass>();
myclass.Solve(...);

Єдина втрата продуктивності при будівництві похідного об'єкта, перший час досить повільний, оскільки він буде використовувати багато випромінювання та відображення. Усі інші часи це вартість пошуку паралельної таблиці та конструктора. Як було сказано, ви можете оптимізувати будівництво за допомогою

ConcurrentDictionary<Type, Func<object>>.

1
Хм .. що ще потребує роботи від імені програміста, щоб активно знати про розподілену обробку; Я шукав рішення, яке покладається лише на них, встановлюючи атрибут [Distributed] для методу (а не підкласифікацію або успадковування від ContextBoundObject). Схоже, мені може знадобитися зробити кілька змін після складання на збірках за допомогою Mono.Cecil або чогось подібного.
Червень Родос

Я б не сказав, що це звичайний спосіб. Цей спосіб простий з точки зору необхідних навичок (не потрібно розуміти CLR), але він повинен повторювати однакові кроки для кожного заміненого методу / класу. Якщо пізніше ви хочете щось змінити (наприклад, виконати якийсь код після, а не тільки раніше), вам доведеться зробити це N разів (на відміну від небезпечного коду, який вимагає зробити це один раз). Отож це N години роботи проти 1 години роботи)
Євген Горбовий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.