Substituir dinamicamente o conteúdo de um método C #?

108

O que eu quero fazer é mudar a forma como um método C # é executado quando chamado, para que eu possa escrever algo assim:

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

Em tempo de execução, preciso ser capaz de analisar métodos que tenham o atributo Distribuído (o que já posso fazer) e inserir o código antes que o corpo da função seja executado e depois que a função retornar. Mais importante, eu preciso ser capaz de fazer isso sem modificar o código onde Solve é chamado ou no início da função (em tempo de compilação; fazer isso em tempo de execução é o objetivo).

No momento, tentei este trecho de código (suponha que t seja o tipo em que Solve está armazenado e m é um 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;
}

No entanto, MethodRental.SwapMethodBody funciona apenas em módulos dinâmicos; não aqueles que já foram compilados e armazenados na montagem.

Portanto, estou procurando uma maneira de fazer SwapMethodBody com eficácia em um método que já está armazenado em um assembly carregado e em execução .

Observe, não é um problema se eu tiver que copiar completamente o método em um módulo dinâmico, mas, neste caso, preciso encontrar uma maneira de copiar através do IL, bem como atualizar todas as chamadas para Solve () de forma que elas apontaria para a nova cópia.

Junho de rodes
fonte
3
Não é possível trocar métodos já carregados. Caso contrário, Spring.Net não teria que fazer coisas estranhas com proxies e interfaces :-) Leia esta pergunta, é tangente ao seu problema: stackoverflow.com/questions/25803/… (se você pode interceptá-lo, você pode algo parecido com -trocar ... Se você não pode 1, então claramente você não pode 2).
xanatos,
Nesse caso, há uma maneira de copiar um método em um módulo dinâmico e atualizar o resto do assembly de forma que as chamadas para esse método apontem para a nova cópia?
Junho, Rodes,
O mesmo de antes. Sem novidades. Se isso pudesse ser feito facilmente, todos os vários contêineres de IoC provavelmente o fariam. Eles não fazem isso-> 99% não pode ser feito :-) (sem hacks terríveis e inomináveis). Há uma única esperança: eles prometeram metaprogramação e assíncrono em C # 5.0. Async vimos ... Nada de metaprogramação ... MAS pode ser isso!
xanatos,
1
Você realmente não explicou por que deseja se envolver em algo tão doloroso.
DanielOfTaebl
6
Por favor, veja minha resposta abaixo. Isso é totalmente possível. Em código que você não possui e durante o tempo de execução. Não entendo por que tantos pensam que isso não é possível.
Andreas Pardeike,

Respostas:

201

Divulgação: Harmony é uma biblioteca que foi escrita e mantida por mim, o autor deste post.

Harmony 2 é uma biblioteca de código aberto (licença MIT) projetada para substituir, decorar ou modificar métodos C # existentes de qualquer tipo durante o tempo de execução. Seu foco principal são jogos e plug-ins escritos em Mono ou .NET. Ele cuida de várias alterações no mesmo método - elas se acumulam em vez de se sobrescrever.

Ele cria métodos de substituição dinâmicos para cada método original e emite código para eles que chama métodos personalizados no início e no final. Ele também permite que você escreva filtros para processar o código IL original e manipuladores de exceção personalizados que permitem uma manipulação mais detalhada do método original.

Para completar o processo, ele grava um salto simples do montador no trampolim do método original que aponta para o montador gerado a partir da compilação do método dinâmico. Isso funciona para 32/64 bits no Windows, macOS e qualquer Linux compatível com o Mono.

A documentação pode ser encontrada aqui .

Exemplo

( Fonte )

Código Original

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

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

Patching with Harmony Annotations

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, remendo manual com reflexão

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
fonte
Dei uma olhada no código fonte, muito interessante! Você pode explicar (aqui e / ou na documentação) como funcionam as instruções específicas que são usadas para realizar o salto (in Memory.WriteJump)?
Tom,
Para responder parcialmente ao meu próprio comentário: 48 B8 <QWord>move um valor imediato de QWord para rax, então FF E0está jmp rax- tudo limpo aí! Minha pergunta restante é sobre o E9 <DWord>caso (um salto para perto): parece que neste caso o salto para perto é preservado e a modificação está no alvo do salto; Quando o Mono gera esse código em primeiro lugar, e por que ele recebe esse tratamento especial?
Tom,
1
Pelo que eu posso dizer, ele ainda não oferece suporte ao .NET Core 2, obtendo algumas exceções com AppDomain.CurrentDomain.DefineDynamicAssembly
Máximo de
1
Um amigo meu, 0x0ade, mencionou para mim que há uma alternativa menos madura que funciona no .NET Core, ou seja, MonoMod.RuntimeDetour no NuGet.
Andreas Pardeike
1
Atualização: incluindo uma referência a System.Reflection.Emit, o Harmony agora compila e testa OK com .NET Core 3
Andreas Pardeike
181

Para .NET 4 e 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
fonte
14
Isso merece muitos outros votos positivos. Eu tenho um cenário completamente diferente, mas este trecho é exatamente o que eu precisava para me colocar na direção certa. Obrigado.
SC
2
Ótima resposta do @Logman. Mas minha pergunta é: o que está acontecendo no modo de depuração? E é possível substituir apenas uma instrução? Por exemplo, se eu quiser substituir o salto condicional por um incondicional? AFAIK você está substituindo o método compilado, então não é fácil determinar qual condição devemos substituir ...
Alex Zhukovskiy
2
@AlexZhukovskiy se você gosta, poste na pilha e me envie o link. Vou investigar e dar uma resposta após o fim de semana. Máquina Vou analisar sua pergunta depois do fim de semana.
Logman
2
Duas coisas que notei ao fazer isso para um teste de integração com MSTest: (1) Quando você usa thisdentro injectionMethod*(), fará referência a uma Injectioninstância durante o tempo de compilação , mas uma Targetinstância durante o tempo de execução (isso é verdadeiro para todas as referências a membros de instância que você usa dentro de um injetado método). (2) Por alguma razão, a #DEBUGparte estava funcionando apenas ao depurar um teste, mas não ao executar um teste que foi compilado para depuração. Acabei sempre usando a #elsepeça. Eu não entendo por que isso funciona, mas funciona.
Good Night Nerd Pride,
2
muito agradável. hora de quebrar tudo! @GoodNightNerdPride use em Debugger.IsAttachedvez do #if pré
M.kazem Akhgary
25

Você PODE modificar o conteúdo de um método em tempo de execução. Mas você não deve fazer isso, e é altamente recomendável mantê-lo para fins de teste.

Basta dar uma olhada em:

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

Basicamente, você pode:

  1. Obtenha o conteúdo do método IL por meio de MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Mexa com esses bytes.

    Se você deseja apenas pré-anexar ou anexar algum código, apenas preprend / anexar opcodes que você deseja (no entanto, tenha cuidado ao deixar a pilha limpa)

    Aqui estão algumas dicas para "descompilar" IL existente:

    • Bytes retornados são uma sequência de instruções IL, seguidas por seus argumentos (se eles tiverem algum - por exemplo, '.call' tem um argumento: o token de método chamado e '.pop' não tem nenhum)
    • A correspondência entre códigos IL e bytes que você encontra na matriz retornada pode ser encontrada usando OpCodes.YourOpCode.Value (que é o valor real do byte do opcode salvo em sua montagem)
    • Os argumentos anexados após os códigos IL podem ter tamanhos diferentes (de um a vários bytes), dependendo do opcode chamado
    • Você pode encontrar tokens aos quais esses argumentos se referem por meio de métodos apropriados. Por exemplo, se o seu IL contém ".call 354354" (codificado como 28 00 05 68 32 em hexa, 28h = 40 sendo '.call' opcode e 56832h = 354354), o método chamado correspondente pode ser encontrado usando MethodBase.GetMethodFromHandle (354354 )
  3. Uma vez modificado, sua matriz de bytes IL pode ser reinjetada via InjectionHelper.UpdateILCodes (método MethodInfo, byte [] ilCodes) - consulte o link mencionado acima

    Esta é a parte "insegura" ... Funciona bem, mas consiste em hackear mecanismos CLR internos ...

Olivier
fonte
7
Apenas para ser pedante, 354354 (0x00056832) não é um token de metadados válido, o byte de alta ordem deve ser 0x06 (MethodDef), 0x0A (MemberRef) ou 0x2B (MethodSpec). Além disso, o token de metadados deve ser escrito na ordem de bytes little-endian. Finalmente, o token de metadados é específico do módulo e MethodInfo.MetadataToken retornará o token do módulo declarante, tornando-o inutilizável se você quiser chamar um método não definido no mesmo módulo que o método que está modificando.
Brian Reichle
13

você pode substituí-lo se o método for não virtual, não genérico, não for do tipo genérico, não embutido e em formato de placa 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
fonte
Isso parece muito perigoso. Eu realmente espero que ninguém o use no código de produção.
Brian Reichle
2
Isso é usado por ferramentas de monitoramento de desempenho de aplicativo (APM) e também na produção.
Martin Kersten
1
Obrigado por responder, estou trabalhando em um projeto para oferecer esse tipo de recurso como API de programação orientada a aspectos. Resolvi minha limitação para gerenciar o método virtual e o método genérico em x86 e x64. Deixe-me saber se você precisar de mais detalhes.
Teter28 de
6
O que é a classe Metadados?
Sebastian de
Esta resposta é um pseudo código e está desatualizada. Muitos dos métodos não existem mais.
N-ate
9

Existem algumas estruturas que permitem alterar dinamicamente qualquer método em tempo de execução (eles usam a interface ICLRProfiling mencionada pelo usuário 152949):

Existem também alguns frameworks que brincam com a parte interna do .NET, estes são provavelmente mais frágeis e provavelmente não podem alterar o código embutido, mas por outro lado, eles são totalmente independentes e não exigem que você use um lançador personalizado.

  • Harmony : licenciado pelo MIT. Parece ter sido usado com sucesso em alguns mods de jogo, suporta .NET e Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 e Comercial. O suporte .NET está atualmente marcado como experimental, mas, por outro lado, tem a vantagem de ter suporte comercial.
poizan42
fonte
8

Solução do Logman , mas com interface para troca de corpos de métodos. Além disso, um exemplo mais simples.

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
fonte
1
Isso me deu: Uma exceção do tipo 'System.AccessViolationException' ocorreu em MA.ELCalc.FunctionalTests.dll, mas não foi tratada no código do usuário Informações adicionais: Tentativa de ler ou gravar memória protegida. Isso geralmente é uma indicação de que outra memória está corrompida. ,,, Ao substituir um getter.
N-ate
Recebi a exceção "wapMethodBodies ainda não manipula IntPtr tamanho de 8"
Phong Dao
6

Com base na resposta a esta e outra pergunta, ive veio com esta versão organizada:

// 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
fonte
No momento, esta é a melhor resposta
Eugene Gorbovoy
seria útil adicionar um exemplo de uso
kofifus de
3

Eu sei que não é a resposta exata para sua pergunta, mas a maneira usual de fazer isso é usando a abordagem de fábricas / proxy.

Primeiro, declaramos um 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;
    }
}

Então, podemos declarar um tipo derivado (chamá-lo de 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();

O tipo derivado também pode ser gerado em tempo de execução.

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

A única perda de desempenho é durante a construção do objeto derivado, a primeira vez é bem lento porque vai usar muita reflexão e emissão de reflexão. Todas as outras vezes, é o custo de uma consulta de tabela simultânea e de um construtor. Como disse, você pode otimizar a construção usando

ConcurrentDictionary<Type, Func<object>>.
Salvatore Previti
fonte
1
Hmm .. isso ainda requer trabalho em nome do programador para estar ativamente ciente do processamento distribuído; Eu estava procurando por uma solução que dependesse apenas da definição do atributo [Distribuído] no método (e não de uma subclasse ou herança de ContextBoundObject). Parece que posso precisar fazer algumas modificações pós-compilação nos assemblies usando Mono.Cecil ou algo parecido.
Junho Rodes
Eu não diria que esta é a maneira usual. Esta maneira é simples em termos de habilidades exigidas (não é necessário entender CLR), mas requer a repetição dos mesmos passos para cada método / classe substituída. Se mais tarde você quiser mudar algo (por exemplo, executar algum código depois, não apenas antes), então você terá que fazer isso N vezes (em contraste com o código não seguro que exige fazer isso uma vez). Então é trabalho de N horas vs trabalho de 1 hora)
Eugene Gorbovoy