动态替换 C# 方法的内容?

Posted

技术标签:

【中文标题】动态替换 C# 方法的内容?【英文标题】:Dynamically replace the contents of a C# method? 【发布时间】:2011-11-10 01:50:03 【问题描述】:

我想做的是改变 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是Solve的MethodInfo)

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

注意,如果我必须将方法完全复制到动态模块中,这不是问题,但在这种情况下,我需要找到一种方法来复制整个 IL 以及更新对 Solve() 的所有调用这样他们就会指向新的副本。

【问题讨论】:

无法交换已加载的方法。否则 Spring.Net 不必用代理和接口做奇怪的事情 :-) 阅读这个问题,它与你的问题相切:***.com/questions/25803/…(如果你可以拦截它,你可以交换它......如果你不能 1 那么显然你不能 2)。 在这种情况下,有没有办法将方法复制到动态模块中,并更新程序集的其余部分,以便对该方法的调用指向新副本? 老老实实。如果可以轻松完成,那么所有各种 IoC 容器都可能会这样做。他们不这样做-> 99% 它不能完成:-)(没有可怕和无名的黑客)。只有一个希望:他们承诺在 C# 5.0 中进行元编程和异步。我们已经看到了异步......元编程什么都没有......但它可能是它! 你真的没有解释为什么要让自己陷入如此痛苦的境地。 请看下面我的回答。这是完全可能的。在您不拥有的代码上和运行时。我不明白为什么这么多人认为这是不可能的。 【参考方案1】:

披露:Harmony 是一个由我(本文的作者)编写和维护的库。

Harmony 2 是一个开源库(MIT 许可),旨在在运行时替换、修饰或修改任何类型的现有 C# 方法。它主要关注用 Mono 或 .NET 编写的游戏和插件。它负责对同一方法进行多次更改 - 它们会累积而不是相互覆盖。

它为每个原始方法创建动态替换方法,并向它们发出代码,在开始和结束时调用自定义方法。它还允许您编写过滤器来处理原始 IL 代码和自定义异常处理程序,从而允许对原始方法进行更详细的操作。

为了完成这个过程,它编写了一个简单的汇编程序跳转到原始方法的蹦床中,指向从编译动态方法生成的汇编程序。这适用于 Windows、macOS 和 Mono 支持的任何 Linux 上的 32/64 位。

文档可以在here找到。

示例

(Source)

原始代码

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 System.Reflection;
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)? 据我所知,它还不支持 .NET Core 2,AppDomain.CurrentDomain.DefineDynamicAssembly 出现了一些异常 我的一个朋友 0x0ade 确实向我提到,有一个不太成熟的替代方案适用于 .NET Core,即 NuGet 上的 MonoMod.RuntimeDetour。 更新:通过包含对 System.Reflection.Emit 的引用,Harmony 现在可以使用 .NET Core 3 进行编译和测试 @rraallvv 要替换方法,您将创建一个前缀,其中包含原始的所有参数加上__instance(如果不是静态)和ref __result,并让它返回 false 以跳过原始.在其中,您使用 __instance 并将结果分配给 __result 然后返回 false。【参考方案2】:

适用于 .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");
        
    


【讨论】:

这值得更多的支持。我有一个完全不同的场景,但是这个 sn-p 正是我需要让我朝着正确的方向前进。谢谢。 @Logman 很好的答案。但我的问题是:调试模式下发生了什么?是否可以只替换一条指令?例如,如果我想替换无条件跳转的条件跳转? AFAIK 您正在替换已编译的方法,因此确定我们应该替换哪个条件并不容易...... @AlexZhukovskiy 如果你喜欢把它贴在堆栈上并发给我链接。我会调查一下,周末后给你答复。机器 周末后我也会调查你的问题。 在使用 MSTest 进行集成测试时,我注意到了两件事:(1) 当您在 injectionMethod*() 中使用 this 时,它将在 编译时引用 Injection 实例,但在 runtime 期间是一个 Target 实例(对于您在注入方法中使用的所有对实例成员的引用都是如此)。 (2) 出于某种原因,#DEBUG 部分仅在 debugging 测试时起作用,但在 running 已调试编译的测试时不起作用。我最终总是使用#else 部分。我不明白为什么这有效,但确实有效。 非常好。是时候打破一切了! @GoodNightNerdPride 使用 Debugger.IsAttached 而不是 #if 预处理器【参考方案3】:

您可以在运行时修改方法的内容。但您不应该这样做,强烈建议您保留它以用于测试目的。

看看:

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

基本上,您可以:

    通过 MethodInfo.GetMethodBody().GetILAsByteArray() 获取 IL 方法内容

    弄乱这些字节。

    如果您只想添加或附加一些代码,那么只需添加/添加您想要的操作码(不过要小心保持堆栈干净)

    以下是一些“反编译”现有 IL 的技巧:

    返回的字节是一系列 IL 指令,后跟它们的参数(如果有的话 - 例如,'.call' 有一个参数:被调用的方法标记,而 '.pop' 没有) 可以使用 OpCodes.YourOpCode.Value 找到您在返回数组中找到的 IL 代码和字节之间的对应关系(这是保存在程序集中的实际操作码字节值) 附加在 IL 代码之后的参数可能有不同的大小(从一个字节到几个字节),具体取决于调用的操作码 您可以通过适当的方法找到这些参数所引用的标记。例如,如果您的 IL 包含“.call 354354”(在 hexa 中编码为 28 00 05 68 32,28h=40 为 '.call' 操作码和 56832h=354354),则可以使用 MethodBase.GetMethodFromHandle(354354 )

    修改后,您的 IL 字节数组可以通过 InjectionHelper.UpdateILCodes(MethodInfo method, byte[] ilCodes) 重新注入 - 请参阅上面提到的链接

    这是“不安全”的部分...它运作良好,但这包括破解内部 CLR 机制...

【讨论】:

只是为了迂腐,354354 (0x00056832) 不是有效的元数据令牌,高位字节应为 0x06 (MethodDef)、0x0A (MemberRef) 或 0x2B (MethodSpec)。此外,元数据令牌应该以 little-endian 字节顺序写入。最后,元数据令牌是特定于模块的,MethodInfo.MetadataToken 将从声明模块返回令牌,如果您想调用未在与您正在修改的方法相同的模块中定义的方法,则它无法使用。【参考方案4】:

如果方法是非虚拟、非泛型、非泛型、非内联且在 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.

【讨论】:

这看起来非常危险。我真的希望没有人在生产代码中使用它。 这被应用程序性能监控 (APM) 工具使用,也用于生产。 感谢您的回复,我正在开发一个项目来提供这种功能作为面向方面的编程 API。我解决了在 x86 和 x64 上管理虚拟方法和泛型方法的限制。如果您需要更多详细信息,请告诉我。 什么是元数据类? 这个答案是伪代码并且已经过时了。许多方法已不复存在。【参考方案5】:

根据对这个问题和另一个问题的回答,我想出了这个整理后的版本:

// 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
        
    

【讨论】:

目前这是最好的答案 添加使用示例会有所帮助 太棒了!我刚试过,它可以工作〜0〜但我想知道它是如何工作的。你能告诉我一些关于它的事情吗?让我们找到答案的链接或主题? 不适用于动态生成的 methodToInject。抛出一切,如 AccessViolationException、内部 CLR 错误、SEXException 等 哦,对不起。需要明确的是,当附加调试器时,它不适用于动态生成的 methodToInject【参考方案6】:

有几个框架允许您在运行时动态更改任何方法(它们使用 user152949 提到的 ICLRProfiling 接口):

Prig:免费开源! Microsoft Fakes:商业版,包含在 Visual Studio Premium 和 Ultimate 中,但不包括 Community 和 Professional Telerik JustMock:商业版,“精简版”可用 Typemock Isolator: 商业

还有一些框架可以模拟 .NET 的内部结构,它们可能更脆弱,并且可能无法更改内联代码,但另一方面,它们是完全独立的,不需要您使用自定义启动器。

Harmony:麻省理工学院许可。似乎实际上已经在一些游戏模组中成功使用,同时支持 .NET 和 Mono。 Deviare In Process Instrumentation Engine:GPLv3 和商业。 .NET 支持目前被标记为实验性的,但另一方面也有商业支持的好处。

【讨论】:

【参考方案7】:

Logman's solution,但带有用于交换方法体的接口。还有一个更简单的例子。

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;
        
    

【讨论】:

这给了我:在 MA.ELCalc.FunctionalTests.dll 中发生了“System.AccessViolationException”类型的异常,但未在用户代码中处理其他信息:尝试读取或写入受保护的内存。这通常表明其他内存已损坏。,,,更换吸气剂时。 我收到异常“wapMethodBodies 尚未处理 8 的 IntPtr 大小”【参考方案8】:

您可以使用ICLRPRofiling Interface 在运行时替换方法。

    调用AttachProfiler 附加到进程。 调用SetILFunctionBody替换方法代码。

See this blog了解更多详情。

【讨论】:

【参考方案9】:

基于TakeMeAsAGuest's answer,这里有一个类似的扩展,不需要使用unsafe 块。

这是Extensions 类:

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

namespace MethodRedirect

    static class Extensions
     
        public static void RedirectTo(this MethodInfo origin, MethodInfo target)
        
            IntPtr ori = GetMethodAddress(origin);
            IntPtr tar = GetMethodAddress(target);
         
            Marshal.Copy(new IntPtr[]  Marshal.ReadIntPtr(tar) , 0, ori, 1);
        

        private static IntPtr GetMethodAddress(MethodInfo mi)
        
            const ushort SLOT_NUMBER_MASK = 0xfff; // 3 bytes
            const int MT_OFFSET_32BIT = 0x28;      // 40 bytes
            const int MT_OFFSET_64BIT = 0x40;      // 64 bytes

            IntPtr address;

            // JIT compilation of the method
            RuntimeHelpers.PrepareMethod(mi.MethodHandle);

            IntPtr md = mi.MethodHandle.Value;             // MethodDescriptor address
            IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address

            if (mi.IsVirtual)
            
                // The fixed-size portion of the MethodTable structure depends on the process type
                int offset = IntPtr.Size == 4 ? MT_OFFSET_32BIT : MT_OFFSET_64BIT;

                // First method slot = MethodTable address + fixed-size offset
                // This is the address of the first method of any type (i.e. ToString)
                IntPtr ms = Marshal.ReadIntPtr(mt + offset);

                // Get the slot number of the virtual method entry from the MethodDesc data structure
                // Remark: the slot number is represented on 3 bytes
                long shift = Marshal.ReadInt64(md) >> 32;
                int slot = (int)(shift & SLOT_NUMBER_MASK);
                
                // Get the virtual method address relative to the first method slot
                address = ms + (slot * IntPtr.Size);                                
            
            else
            
                // Bypass default MethodDescriptor padding (8 bytes) 
                // Reach the CodeOrIL field which contains the address of the JIT-compiled code
                address = md + 8;
            

            return address;
        
    

下面是一个简单的用法示例:

using System;
using System.Reflection;

namespace MethodRedirect

    class Scenario
        
      static void Main(string[] args)
      
          Assembly assembly = Assembly.GetAssembly(typeof(Scenario));
          Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario");

          MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);
          MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);

          Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod);

          // Using dynamic type to prevent method string caching
          dynamic scenario = (Scenario)Activator.CreateInstance(Scenario_Type);

          bool result = scenario.InternalInstanceMethod() == "PrivateInstanceMethod";

          Console.WriteLine("\nRedirection 0", result ? "SUCCESS" : "FAILED");

          Console.ReadKey();
      

      internal string InternalInstanceMethod()
      
          return "InternalInstanceMethod";
      

      private string PrivateInstanceMethod()
      
          return "PrivateInstanceMethod";
      
    

这是从我在 Github (MethodRedirect) 上提供的一个更详细的项目中提炼出来的。

【讨论】:

【参考方案10】:

我知道这不是您问题的确切答案,但通常的方法是使用工厂/代理方法。

首先我们声明一个基类型。

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>>.

【讨论】:

嗯.. 仍然需要以程序员的名义进行工作以主动了解分布式处理;我正在寻找一种仅依赖于他们在方法上设置 [Distributed] 属性的解决方案(而不是从 ContextBoundObject 继承或继承)。看起来我可能需要使用 Mono.Cecil 或类似的东西对程序集进行一些编译后修改。 我不会说,这是通常的方式。这种方式在所需技能方面很简单(无需了解 CLR),但需要对每个替换的方法/类重复相同的步骤。如果稍后您想更改某些内容(例如,在之后执行某些代码,而不仅仅是之前),那么您将不得不执行 N 次(与需要执行一次的不安全代码相反)。所以这是 N 小时的工作与 1 小时的工作)【参考方案11】:

看看 Mono.Cecil:

using Mono.Cecil;
using Mono.Cecil.Inject;

public class Patcher
    
   public void Patch()
   
    // Load the assembly that contains the hook method
    AssemblyDefinition hookAssembly = AssemblyLoader.LoadAssembly("MyHookAssembly.dll");
    // Load the assembly
    AssemblyDefinition targetAssembly = AssemblyLoader.LoadAssembly("TargetAssembly.dll");

    // Get the method definition for the injection definition
    MethodDefinition myHook = hookAssembly.MainModule.GetType("HookNamespace.MyHookClass").GetMethod("MyHook");
    // Get the method definition for the injection target. 
    // Note that in this example class Bar is in the global namespace (no namespace), which is why we don't specify the namespace.
    MethodDefinition foo = targetAssembly.MainModule.GetType("Bar").GetMethod("Foo");

    // Create the injector
    InjectionDefinition injector = new InjectionDefinition(foo, myHook, InjectFlags.PassInvokingInstance | InjectFlags.passParametersVal);

    // Perform the injection with default settings (inject into the beginning before the first instruction)
    injector.Inject();

    // More injections or saving the target assembly...
   

【讨论】:

以上是关于动态替换 C# 方法的内容?的主要内容,如果未能解决你的问题,请参考以下文章

.NET(C#) Clipboard剪贴板内容(文本和Html)提取和替换方法及示例代码

在 C# 中动态替换 DLL

BIRT - 动态替换标签内容

正则表达式 替换 html 内容 C#

C#正则表达式指定替换

如何在 c# 中替换 xml 开始标记和 xml 结束标记之间的内容?