¿Reemplazar dinámicamente el contenido de un método C #?

108

Lo que quiero hacer es cambiar la forma en que se ejecuta un método de C # cuando se llama, para poder escribir algo como esto:

[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;
}

En tiempo de ejecución, necesito poder analizar métodos que tienen el atributo Distribuido (lo cual ya puedo hacer) y luego insertar código antes de que se ejecute el cuerpo de la función y después de que la función regrese. Más importante aún, necesito poder hacerlo sin modificar el código donde se llama a Solve o al comienzo de la función (en tiempo de compilación; hacerlo en tiempo de ejecución es el objetivo).

Por el momento, he intentado este bit de código (suponga que t es el tipo en el que está almacenado Solve y m es un MethodInfo de Solve) :

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;
}

Sin embargo, MethodRental.SwapMethodBody solo funciona en módulos dinámicos; no los que ya se han compilado y almacenado en el ensamblaje.

Así que estoy buscando una manera de hacer SwapMethodBody de manera efectiva en un método que ya está almacenado en un ensamblado cargado y en ejecución .

Tenga en cuenta que no es un problema si tengo que copiar completamente el método en un módulo dinámico, pero en este caso necesito encontrar una manera de copiar a través de IL, así como actualizar todas las llamadas a Solve () de modo que apuntaría a la nueva copia.

Junio ​​de rodas
fuente
3
No es posible intercambiar métodos ya cargados. De lo contrario, Spring.Net no tendría que hacer cosas extrañas con proxies e interfaces :-) Lea esta pregunta, es tangente a su problema: stackoverflow.com/questions/25803/… (si puede interceptarlo, puede hacer algo como -cámbielo ... Si no puede 1, entonces claramente no puede 2).
xanatos
En ese caso, ¿hay alguna forma de copiar un método en un módulo dinámico y actualizar el resto del ensamblado de modo que las llamadas a ese método apunten a la nueva copia?
Junio ​​Rodas
El mismo de siempre. Si pudiera hacerse fácilmente, probablemente todos los contenedores de IoC lo harían. No lo hacen-> 99% no se puede hacer :-) (sin hacks terribles e innominables). Hay una sola esperanza: prometieron metaprogramación y async en C # 5.0. Async que hemos visto ... Metaprogramación nada ... ¡PERO podría ser!
xanatos
1
Realmente no ha explicado por qué quiere dejarse llevar por algo tan doloroso.
DanielOfTaebl
6
Por favor vea mi respuesta a continuación. Esto es totalmente posible. En el código que no posee y durante el tiempo de ejecución. No entiendo por qué tantos piensan que esto no es posible.
Andreas Pardeike

Respuestas:

201

Divulgación: Harmony es una biblioteca que fue escrita y mantenida por mí, el autor de esta publicación.

Harmony 2 es una biblioteca de código abierto (licencia MIT) diseñada para reemplazar, decorar o modificar métodos de C # existentes de cualquier tipo durante el tiempo de ejecución. Su enfoque principal son los juegos y complementos escritos en Mono o .NET. Se encarga de varios cambios en el mismo método: se acumulan en lugar de sobrescribirse entre sí.

Crea métodos de reemplazo dinámicos para cada método original y les emite un código que llama a métodos personalizados al principio y al final. También le permite escribir filtros para procesar el código IL original y los manejadores de excepciones personalizados, lo que permite una manipulación más detallada del método original.

Para completar el proceso, escribe un simple salto de ensamblador en el trampolín del método original que apunta al ensamblador generado a partir de la compilación del método dinámico. Esto funciona para 32 / 64Bit en Windows, macOS y cualquier Linux compatible con Mono.

La documentación se puede encontrar aquí .

Ejemplo

( Fuente )

Codigo original

public class SomeGameClass
{
    private bool isRunning;
    private int counter;

    private int DoSomething()
    {
        if (isRunning)
        {
            counter++;
            return counter * 10;
        }
    }
}

Parcheo con anotaciones 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;
    }
}

Alternativamente, parcheo manual con reflexión

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");

        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()
    {
        // ...
    }
}
Andreas Pardeike
fuente
Eché un vistazo al código fuente, ¡muy interesante! ¿Puedes explicar (aquí y / o en la documentación) cómo funcionan las instrucciones específicas que se utilizan para realizar el salto (in Memory.WriteJump)?
Tom
Para responder parcialmente a mi propio comentario: 48 B8 <QWord>mueve un valor inmediato de QWord a rax, luego FF E0está jmp rax: ¡todo claro allí! Mi pregunta restante es sobre el E9 <DWord>caso (un salto cercano): parece que en este caso el salto cercano se conserva y la modificación está en el objetivo del salto; ¿Cuándo genera Mono tal código en primer lugar y por qué recibe este tratamiento especial?
Tom
1
Por lo que puedo decir, todavía no es compatible con .NET Core 2, obteniendo algunas excepciones con AppDomain.CurrentDomain.DefineDynamicAssembly
Max
1
Un amigo mío, 0x0ade, me mencionó que hay una alternativa menos madura que funciona en .NET Core, a saber, MonoMod.RuntimeDetour en NuGet.
Andreas Pardeike
1
Actualización: al incluir una referencia a System.Reflection.Emit, Harmony ahora compila y prueba
correctamente
181

Para .NET 4 y superior

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");
        }
    }

}
Logman
fuente
14
Esto merece muchos más votos a favor. Tengo un escenario completamente diferente, pero este fragmento es exactamente lo que necesitaba para ponerme en la dirección correcta. Gracias.
SC
2
@Logman gran respuesta. Pero mi pregunta es: ¿Qué está pasando en el modo de depuración? ¿Y es posible reemplazar solo una instrucción? Por ejemplo, si quiero reemplazar el salto condicional en uno incondicional? AFAIK, está reemplazando el método compilado, por lo que no es fácil determinar qué condición debemos reemplazar ...
Alex Zhukovskiy
2
@AlexZhukovskiy, si quieres, publícalo en la pila y envíame el enlace. Lo investigaré y te daré una respuesta después del fin de semana. Máquina también analizaré su pregunta después del fin de semana.
Logman
2
Dos cosas que noté al hacer esto para una prueba de integración con MSTest: (1) Cuando lo use thisdentro injectionMethod*(), hará referencia a una Injectioninstancia durante el tiempo de compilación , pero una Targetinstancia durante el tiempo de ejecución (esto es cierto para todas las referencias a miembros de instancia que usa dentro de un inyectado método). (2) Por alguna razón, la #DEBUGpieza solo funcionaba cuando se depuraba una prueba, pero no cuando se ejecutaba una prueba que había sido depurada y compilada. Terminé siempre utilizando la #elseparte. No entiendo por qué esto funciona, pero funciona.
Good Night Nerd Pride
2
muy agradable. ¡Es hora de romper todo! @GoodNightNerdPride uso en Debugger.IsAttachedlugar de #if preprocesador
M.kazem Akhgary
25

PUEDE modificar el contenido de un método en tiempo de ejecución. Pero se supone que no debe hacerlo, y se recomienda encarecidamente que lo guarde para fines de prueba.

Eche un vistazo a:

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

Básicamente, puedes:

  1. Obtenga el contenido del método IL a través de MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Ensucia estos bytes.

    Si solo desea anteponer o agregar algún código, simplemente anteponga / agregue los códigos de operación que desee (tenga cuidado de dejar la pila limpia)

    A continuación, se ofrecen algunos consejos para "descompilar" IL existente:

    • Los bytes devueltos son una secuencia de instrucciones IL, seguidas de sus argumentos (si tienen alguno, por ejemplo, '.call' tiene un argumento: el token del método llamado y '.pop' no tiene ninguno)
    • La correspondencia entre los códigos IL y los bytes que encuentra en la matriz devuelta se puede encontrar usando OpCodes.YourOpCode.Value (que es el valor real del byte del código de operación tal como se guardó en su ensamblaje)
    • Los argumentos añadidos después de los códigos IL pueden tener diferentes tamaños (de uno a varios bytes), según el código de operación llamado
    • Puede encontrar tokens a los que se refieren estos argumentos a través de métodos apropiados. Por ejemplo, si su IL contiene ".call 354354" (codificado como 28 00 05 68 32 en hexa, 28h = 40 es '.call' opcode y 56832h = 354354), el método llamado correspondiente se puede encontrar usando MethodBase.GetMethodFromHandle (354354 )
  3. Una vez modificada, su matriz de bytes IL se puede reinyectar a través de InjectionHelper.UpdateILCodes (método MethodInfo, byte [] ilCodes) - vea el enlace mencionado anteriormente

    Esta es la parte "insegura" ... Funciona bien, pero esto consiste en hackear los mecanismos internos de CLR ...

Olivier
fuente
7
Para ser pedante, 354354 (0x00056832) no es un token de metadatos válido, el byte de orden superior debe ser 0x06 (MethodDef), 0x0A (MemberRef) o 0x2B (MethodSpec). Además, el token de metadatos debe escribirse en orden de bytes little-endian. Finalmente, el token de metadatos es específico del módulo y MethodInfo.MetadataToken devolverá el token del módulo de declaración, haciéndolo inutilizable si desea llamar a un método no definido en el mismo módulo que el método que está modificando.
Brian Reichle
13

puede reemplazarlo si el método no es virtual, no genérico, no es de tipo genérico, no está en línea y en plataforma 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.
Teter28
fuente
Eso parece muy peligroso. Realmente espero que nadie lo use en código de producción.
Brian Reichle
2
Esto lo utilizan las herramientas de supervisión del rendimiento de las aplicaciones (APM) y también se utiliza en producción.
Martin Kersten
1
Gracias por la respuesta, estoy trabajando en un proyecto para ofrecer este tipo de capacidad como API de programación orientada a aspectos. Resolví mi limitación para administrar el método virtual y el método genérico tanto en x86 como en x64. Déjeme saber si usted necesita más detalles.
Teter28
6
¿Qué es la clase de metadatos?
Sebastian
Esta respuesta es un pseudocódigo y está desactualizada. Muchos de los métodos ya no existen.
N-ate el
9

Existe un par de marcos que le permiten cambiar dinámicamente cualquier método en tiempo de ejecución (utilizan la interfaz ICLRProfiling mencionada por user152949):

También hay algunos marcos que se burlan de los componentes internos de .NET, estos probablemente sean más frágiles y probablemente no puedan cambiar el código en línea, pero por otro lado son completamente autónomos y no requieren que use un lanzador personalizado.

  • Armonía : licencia del MIT. Parece que en realidad se ha utilizado con éxito en algunas modificaciones de juegos, admite tanto .NET como Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 y Comercial. El soporte de .NET actualmente está marcado como experimental, pero por otro lado tiene la ventaja de contar con respaldo comercial.
poizan42
fuente
8

La solución de Logman , pero con una interfaz para intercambiar cuerpos de métodos. Además, un ejemplo más simple.

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;
        }
    }
}
C. McCoy IV
fuente
1
Esto me dio: Se produjo una excepción del tipo 'System.AccessViolationException' en MA.ELCalc.FunctionalTests.dll pero no se manejó en el código de usuario. Información adicional: Intento de leer o escribir en la memoria protegida. Esto suele ser una indicación de que otra memoria está dañada. ,,, Al reemplazar un captador.
N-ate el
Obtuve la excepción "wapMethodBodies aún no maneja el tamaño de IntPtr de 8"
Phong Dao
6

Basándome en la respuesta a esta pregunta y a otra, se me ocurrió esta versión ordenada:

// 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
        }
    }
TakeMeAsAGuest
fuente
Por el momento, esta es la mejor respuesta
Eugene Gorbovoy
Sería útil agregar un ejemplo de uso
kofifus
3

Sé que no es la respuesta exacta a su pregunta, pero la forma habitual de hacerlo es utilizando el enfoque de fábricas / proxy.

Primero declaramos un tipo base.

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;
    }
}

Entonces podemos declarar un tipo derivado (llamarlo proxy).

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();

El tipo derivado también se puede generar en tiempo de ejecución.

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(...);

La única pérdida de rendimiento es durante la construcción del objeto derivado, la primera vez es bastante lenta porque utilizará una gran cantidad de reflexión y emisión de reflexión. Todas las demás veces, es el costo de una consulta de tabla simultánea y un constructor. Como se dijo, puede optimizar la construcción usando

ConcurrentDictionary<Type, Func<object>>.
Salvatore Previti
fuente
1
Hmm ... eso todavía requiere trabajo en nombre del programador para estar activamente al tanto del procesamiento distribuido; Estaba buscando una solución que se base solo en que ellos establezcan el atributo [Distribuido] en el método (y no en subclases o heredar de ContextBoundObject). Parece que podría necesitar hacer algunas modificaciones posteriores a la compilación en los ensamblados usando Mono.Cecil o algo así.
Junio ​​Rodas
No diría que esta es la forma habitual. Esta forma es simple en términos de habilidades requeridas (no es necesario entender CLR), pero requiere repetir los mismos pasos para cada método / clase reemplazado. Si más tarde desea cambiar algo (por ejemplo, ejecutar algún código después, no solo antes), tendrá que hacerlo N veces (en contraste con el código inseguro que requiere hacerlo una vez). Así que es un trabajo de N horas frente a un trabajo de 1 hora)
Eugene Gorbovoy