动态替换 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# 方法的内容?的主要内容,如果未能解决你的问题,请参考以下文章