替换实现接口并从基类继承的声明类型的 C# 方法 [英] Replacing C# method of declaring type which implements an interface and inherits from base
问题描述
为了对遗留代码进行单元测试,我试图在运行时换出方法的内容.我一直在处理这些 SO 答案;
I'm trying to swap out the contents of a method at runtime for the purposes of unit testing legacy code. I've been working with these SO answers;
这是我目前所拥有的完整代码示例.
Here's a full code sample of what I have so far.
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Foo.Bar {
public interface IFoo {
string Apple();
}
public class Bar {
protected virtual object One() {
return null;
}
protected virtual object Two() {
return null;
}
protected virtual object Three() {
return null;
}
/* Uncomment this to generate a null reference */
//protected virtual object Four() {
// return null;
//}
}
public class Foo : Bar, IFoo {
public string Apple() {
return "Apple";
}
public string Orange() {
return "Orange";
}
/* Uncommenting this fixes the null reference */
//public override int GetHashCode() {
// throw new NotImplementedException();
//}
public void ReplaceMethod(Delegate targetMethod, Delegate replacementMethod) {
MethodInfo methodToReplace = targetMethod.Method;
MethodInfo methodToInject = replacementMethod.Method;
RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
if (methodToReplace.IsVirtual)
ReplaceVirtualInner(methodToReplace, methodToInject);
else
ReplaceStandard(methodToReplace, methodToInject);
}
private void ReplaceStandard(MethodInfo methodToReplace, MethodInfo methodToInject) {
IntPtr targetPtr = methodToInject.MethodHandle.Value;
IntPtr replacePtr = methodToReplace.MethodHandle.Value;
unsafe
{
if (IntPtr.Size == 4) {
int* inj = (int*)replacePtr.ToPointer() + 2;
int* tar = (int*)targetPtr.ToPointer() + 2;
if (Debugger.IsAttached) {
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 {
*tar = *inj;
}
}
else {
long* inj = (long*)replacePtr.ToPointer() + 1;
long* tar = (long*)targetPtr.ToPointer() + 1;
if (Debugger.IsAttached) {
byte* injInst = (byte*)*inj;
byte* tarInst = (byte*)*tar;
long* injSrc = (long*)(injInst + 1);
long* tarSrc = (long*)(tarInst + 1);
*tarSrc = (((long)injInst + 5) + *injSrc) - ((long)tarInst + 5);
}
else {
*tar = *inj;
}
}
}
}
private void ReplaceVirtualInner(MethodInfo methodToReplace, MethodInfo methodToInject) {
unsafe
{
UInt64* methodDesc = (UInt64*)(methodToReplace.MethodHandle.Value.ToPointer());
int index = (int)(((*methodDesc) >> 32) & 0xFF);
if (IntPtr.Size == 4) {
uint* classStart = (uint*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
classStart += 10;
classStart = (uint*)*classStart;
uint* tar = classStart + index;
uint* inj = (uint*)methodToInject.MethodHandle.Value.ToPointer() + 2;
if (Debugger.IsAttached) {
byte* injInst = (byte*)*inj;
byte* tarInst = (byte*)*tar;
uint* injSrc = (uint*)(injInst + 1);
uint* tarSrc = (uint*)(tarInst + 1);
*tarSrc = (((uint)injInst + 5) + *injSrc) - ((uint)tarInst + 5);
}
else {
*tar = *inj;
}
}
else {
ulong* classStart = (ulong*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
classStart += 8;
classStart = (ulong*)*classStart;
ulong* tar = classStart + index;
ulong* inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1;
if (Debugger.IsAttached) {
byte* injInst = (byte*)*inj;
byte* tarInst = (byte*)*tar;
ulong* injSrc = (ulong*)(injInst + 1);
ulong* tarSrc = (ulong*)(tarInst + 1);
*tarSrc = (((ulong)injInst + 5) + *injSrc) - ((ulong)tarInst + 5);
}
else {
*tar = *inj;
}
}
}
}
}
}
用法;
Foo.Bar.Foo foo = new Foo.Bar.Foo();
foo.ReplaceMethod(
((Func<string>)foo.Apple),
((Func<string>)foo.Orange)
);
var result = foo.Apple(); // this is "Orange" :)
我对上面的代码有一个广泛的理解,本质上它是定位目标方法的地址并更改值,使其指向不同的内存位置.还有一些额外的伏都教来适应调试器添加的内存缓冲区.
I have a broad understanding of the code above, essentially it's locating the address of the target method and changing the value so that it points to a different memory location. There's also some extra voodoo to accommodate for memory buffers added by the debugger.
虚拟方法的处理方式不同,我不完全理解,尤其是;
Virtual methods are handled differently which I don't fully understand, especially;
uint* classStart = (uint*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
classStart += 10; /* why 10?? */
classStart = (uint*)*classStart;
代码有效,但是,这就是事情变得奇怪的地方;如果基类(目标方法的声明类型)具有超过 3 个虚拟方法,实现了一个接口并且没有覆盖任何方法,则抛出 NullReferenceException.
The code works, however, here's where things get weird; if the base class (of the declaring type of the target method) has more than 3 virtual methods, implements an interface and does not override any methods then a NullReferenceException is thrown.
请谁能解释一下发生了什么并帮助我更深入地了解代码?
Please, can someone explain what's going on and help me gain a deeper understanding of the code?
推荐答案
您所问的实际上是 typemock 中的一项功能,这是一个单元测试框架,允许您通过简单的一行代码将模拟方法的实现更改为另一个代码,例如:
What you are asking is actually a feature in typemock a unit testing framework that allows you to change the implementation of a mocked method to another one in a simple line of code, for example:
[TestMethod]
public void TestMethod1()
{
var real = new Foo();
Isolate.WhenCalled(() => real.Apple()).DoInstead(x => { return real.Orange(); });
Assert.AreEqual("Orange", real.Apple());
}
您可以在此处了解有关此功能的更多信息.
这篇关于替换实现接口并从基类继承的声明类型的 C# 方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!