https://stackoverflow.com/a/42043003/14367566
Dynamically replace the contents of a C# method?
What I want to do is change how a C# method executes when it is called, so that I can write something like this: [Distributed] public DTask<bool> Solve(int n, DEvent<bool> callback) { ...
stackoverflow.com
동적으로 C# 메소드의 내용물을 조작하는 것이 가능할까?
가능하다. Harmony라는 오픈 소스 라이브러리를 사용하면 된다. MIT 라이센스다.
여기 문서화도 되어 있다.
https://harmony.pardeike.net/articles/patching-prefix.html
Patching
Patching Prefix A prefix is a method that is executed before the original method. It is commonly used to: access and edit the arguments of the original method set the result of the original method skip the original method and prefixes that alter its input/
harmony.pardeike.net
진짜 가능한지 확인하기 위해 .NET Framework 4.8.1에서 내가 직접 실험해봤다. Console.WriteLine(string)을 수정해보았다.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using HarmonyLib;
namespace Console481
{
internal static class Program
{
private static void Main()
{
Console.WriteLine("[Hello, world!]");
Console.WriteLine(777);
var harmony = new Harmony("com.example");
var org = AccessTools.Method(typeof(System.Console), nameof(System.Console.WriteLine), new Type[] { typeof(string) });
var pre = SymbolExtensions.GetMethodInfo(() => Pre());
var post = SymbolExtensions.GetMethodInfo(() => Post());
var fin = SymbolExtensions.GetMethodInfo(() => Fin());
harmony.Patch(org, pre, post, null, fin);
Console.WriteLine("[Bye, world!]");
Console.WriteLine(888);
}
public static bool Pre()
{
Console.Write("[pre]");
return true;
}
public static void Post()
{
Console.Write("[post]");
}
public static void Fin()
{
Console.Write("[fin]");
}
}
}
출력은 다음과 같다.
[Hello, world!]
777
[pre][Bye, world!]
[post][fin]888
만약 Console.WriteLine(string value)가 value를 출력하는 것을 막고 싶다면(즉, original method의 동작을 완전히 배제하고 싶다면), Pre 메소드가 false를 반환하게 하여 original method를 skip하게 하면 된다. 그때 출력은 다음과 같다.
[Hello, world!]
777
[pre][post][fin]888
반환 타입이 void가 아닌 메소드를 조작할 경우 아래와 같이 ref __result를 사용할 수 있다. 아래 코드는 int.Parse의 원래 동작은 유지하되 "apple"을 넣을 경우 55555가 반환되도록 하는 코드이다.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using HarmonyLib;
namespace Console481
{
internal static class Program
{
private static void Main()
{
var harmony = new Harmony("com.example");
var org = AccessTools.Method(typeof(int), nameof(int.Parse), new Type[] { typeof(string) });
var pre = typeof(Program).GetMethod("Pre");
var post = SymbolExtensions.GetMethodInfo(() => Post());
var fin = SymbolExtensions.GetMethodInfo(() => Fin());
harmony.Patch(org, pre, post, null, fin);
Console.WriteLine(int.Parse("987654321"));
Console.WriteLine(int.Parse("apple"));
Console.WriteLine(int.Parse("1234567890"));
}
public static bool Pre(string s, ref int __result)
{
Console.Write("[pre]");
if(s == "apple")
{
__result = 55555;
return false;
}
return true;
}
public static void Post()
{
Console.Write("[post]");
}
public static void Fin()
{
Console.Write("[fin]");
}
}
}
출력은 다음과 같다.
[pre][post][fin]987654321
[pre][post][fin]55555
[pre][post][fin]1234567890