C#

Dynamically replace the contents of a C# method?

falto 2025. 8. 14. 13:18

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