Sostituire dinamicamente il contenuto di un metodo C #?


109

Quello che voglio fare è cambiare il modo in cui un metodo C # viene eseguito quando viene chiamato, in modo da poter scrivere qualcosa del genere:

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

In fase di esecuzione, devo essere in grado di analizzare i metodi che hanno l'attributo Distributed (cosa che posso già fare) e quindi inserire il codice prima che il corpo della funzione venga eseguito e dopo che la funzione ritorna. Ancora più importante, devo essere in grado di farlo senza modificare il codice in cui viene chiamato Solve o all'inizio della funzione (in fase di compilazione; farlo in fase di esecuzione è l'obiettivo).

Al momento ho provato questo bit di codice (supponiamo che t sia il tipo in cui è memorizzato Solve e m sia un MethodInfo di 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;
}

Tuttavia, MethodRental.SwapMethodBody funziona solo su moduli dinamici; non quelli che sono già stati compilati e memorizzati nell'assembly.

Quindi sto cercando un modo per eseguire in modo efficace SwapMethodBody su un metodo che è già archiviato in un assembly caricato ed in esecuzione .

Nota, non è un problema se devo copiare completamente il metodo in un modulo dinamico, ma in questo caso devo trovare un modo per copiare attraverso l'IL e aggiornare tutte le chiamate a Solve () in modo che indicherebbe la nuova copia.


3
Non è possibile scambiare metodi già caricati. Altrimenti Spring.Net non dovrebbe fare cose strane con proxy e interfacce :-) Leggi questa domanda, è tangente al tuo problema: stackoverflow.com/questions/25803/… (se puoi intercettarlo, puoi fare qualcosa di simile -scambialo ... Se non puoi 1 allora chiaramente non puoi 2).
xanatos

In tal caso, esiste un modo per copiare un metodo in un modulo dinamico e aggiornare il resto dell'assembly in modo che le chiamate a quel metodo puntino alla nuova copia?
June Rhodes

Stesso vecchio stesso vecchio. Se potesse essere fatto facilmente, probabilmente lo farebbero tutti i vari contenitori IoC. Non lo fanno-> 99% non può essere fatto :-) (senza hack terribili e innominabili). C'è una sola speranza: hanno promesso la metaprogrammazione e l'asincronia in C # 5.0. Async abbiamo visto ... Metaprogrammazione di nulla ... MA potrebbe essere!
xanatos

1
Non hai davvero spiegato perché vuoi lasciarti andare a qualcosa di così doloroso.
DanielOfTaebl

6
Si prega di vedere la mia risposta di seguito. Questo è totalmente possibile. Su codice che non possiedi e durante il runtime. Non capisco perché così tanti pensano che questo non sia possibile.
Andreas Pardeike

Risposte:


202

Divulgazione: Harmony è una libreria che è stata scritta ed è gestita da me, l'autore di questo post.

Harmony 2 è una libreria open source (licenza MIT) progettata per sostituire, decorare o modificare metodi C # esistenti di qualsiasi tipo durante il runtime. L'obiettivo principale sono giochi e plug-in scritti in Mono o .NET. Si occupa di più modifiche allo stesso metodo: si accumulano invece di sovrascriverle a vicenda.

Crea metodi di sostituzione dinamici per ogni metodo originale ed emette codice che chiama metodi personalizzati all'inizio e alla fine. Consente inoltre di scrivere filtri per elaborare il codice IL originale e gestori di eccezioni personalizzati che consentono una manipolazione più dettagliata del metodo originale.

Per completare il processo, scrive un semplice salto dell'assemblatore nel trampolino del metodo originale che punta all'assemblatore generato dalla compilazione del metodo dinamico. Funziona per 32 / 64Bit su Windows, macOS e qualsiasi Linux supportato da Mono.

La documentazione può essere trovata qui .

Esempio

( Fonte )

Codice originale

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

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

Patch con annotazioni 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;
    }
}

In alternativa, patch manuale con riflessione

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()
    {
        // ...
    }
}

Ho dato un'occhiata al codice sorgente, molto interessante! Puoi spiegare (qui e / o nella documentazione) come funzionano le istruzioni specifiche utilizzate per eseguire il salto (in Memory.WriteJump)?
Tom

Per rispondere parzialmente al mio commento: 48 B8 <QWord>sposta un valore immediato di QWord su rax, quindi FF E0è jmp raxtutto chiaro lì! La mia domanda rimanente riguarda il E9 <DWord>caso (un salto vicino): sembra che in questo caso il salto vicino sia conservato e la modifica sia sul bersaglio del salto; quando Mono genera questo codice in primo luogo e perché riceve questo trattamento speciale?
Tom

1
Per quanto posso dire, non supporta ancora .NET Core 2, ottenendo alcune eccezioni con AppDomain.CurrentDomain.DefineDynamicAssembly
Max

1
Un mio amico, 0x0ade mi ha detto che esiste un'alternativa meno matura che funziona su .NET Core, ovvero MonoMod.RuntimeDetour su NuGet.
Andreas Pardeike

1
Aggiornamento: includendo un riferimento a System.Reflection.Emit, Harmony ora compila e
testa

181

Per .NET 4 e versioni successive

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

}

14
Questo merita tanti altri voti positivi. Ho uno scenario completamente diverso, ma questo snippet è esattamente ciò di cui avevo bisogno per mettermi nella giusta direzione. Grazie.
SC

2
@ Logman ottima risposta. Ma la mia domanda è: cosa sta succedendo in modalità debug? Ed è possibile sostituire solo un'istruzione? Ad esempio, se voglio sostituire il salto condizionale con quello incondizionato? AFAIK stai sostituendo il metodo compilato, quindi non è facile determinare quale condizione dovremmo sostituire ...
Alex Zhukovskiy

2
@AlexZhukovskiy se ti piace postalo sullo stack e inviami il link. Lo esaminerò e ti darò una risposta dopo il fine settimana. Macchina esaminerò anche la tua domanda dopo il fine settimana.
Logman

2
Due cose che ho notato quando lo facevo per un test di integrazione con MSTest: (1) Quando lo usi thisal injectionMethod*()suo interno farà riferimento a Injectionun'istanza durante la fase di compilazione , ma Targetun'istanza durante il runtime (questo è vero per tutti i riferimenti ai membri dell'istanza che usi all'interno di un metodo). (2) Per qualche ragione la #DEBUGparte funzionava solo durante il debug di un test, ma non durante l' esecuzione di un test che è stato debug-compilato. Ho finito per usare sempre la #elseparte. Non capisco perché funziona ma funziona.
Good Night Nerd Pride

2
molto bella. è tempo di rompere tutto! @GoodNightNerdPride usa al Debugger.IsAttachedposto del #if preprocessore
M.kazem Akhgary

25

È POSSIBILE modificare il contenuto di un metodo in fase di esecuzione. Ma non dovresti, e si consiglia vivamente di tenerlo a scopo di test.

Dai un'occhiata a:

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

Fondamentalmente puoi:

  1. Ottieni il contenuto del metodo IL tramite MethodInfo.GetMethodBody (). GetILAsByteArray ()
  2. Pasticciare con questi byte.

    Se desideri solo anteporre o aggiungere un codice, preprend / aggiungi gli opcodes che desideri (fai attenzione a lasciare lo stack pulito, però)

    Di seguito sono riportati alcuni suggerimenti per "non compilare" l'IL esistente:

    • I byte restituiti sono una sequenza di istruzioni IL, seguite dai loro argomenti (se ne hanno alcuni, ad esempio ".call" ha un argomento: il token del metodo chiamato e ".pop" non ne ha)
    • La corrispondenza tra i codici IL e i byte che trovi nell'array restituito può essere trovata utilizzando OpCodes.YourOpCode.Value (che è il valore reale del byte del codice operativo salvato nell'assembly)
    • Gli argomenti aggiunti dopo i codici IL possono avere dimensioni diverse (da uno a diversi byte), a seconda del codice operativo chiamato
    • È possibile trovare token a cui si riferiscono questi argomenti tramite metodi appropriati. Ad esempio, se il tuo IL contiene ".call 354354" (codificato come 28 00 05 68 32 in hexa, 28h = 40 è il codice operativo '.call' e 56832h = 354354), il metodo chiamato corrispondente può essere trovato utilizzando MethodBase.GetMethodFromHandle (354354 )
  3. Una volta modificato, l'array di byte IL può essere reiniettato tramite InjectionHelper.UpdateILCodes (metodo MethodInfo, byte [] ilCodes) - vedere il collegamento menzionato sopra

    Questa è la parte "non sicura" ... Funziona bene, ma consiste nell'hacking dei meccanismi interni di CLR ...


7
Giusto per essere pedanti, 354354 (0x00056832) non è un token di metadati valido, il byte di ordine superiore dovrebbe essere 0x06 (MethodDef), 0x0A (MemberRef) o 0x2B (MethodSpec). Inoltre, il token dei metadati dovrebbe essere scritto in ordine di byte little-endian. Infine, il token dei metadati è specifico del modulo e MethodInfo.MetadataToken restituirà il token dal modulo dichiarante, rendendolo inutilizzabile se si desidera chiamare un metodo non definito nello stesso modulo del metodo che si sta modificando.
Brian Reichle

13

puoi sostituirlo se il metodo è non virtuale, non generico, non di tipo generico, non inline e su piattaforma 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.

Sembra follemente pericoloso. Spero davvero che nessuno lo usi nel codice di produzione.
Brian Reichle

2
Questo viene utilizzato dagli strumenti APM (Application Performance Monitoring) e viene utilizzato anche in produzione.
Martin Kersten

1
Grazie per la risposta, sto lavorando a un progetto per offrire questo tipo di funzionalità come API di programmazione orientata agli aspetti. Ho risolto la mia limitazione per gestire il metodo virtuale e il metodo generico su x86 e x64. Fammi sapere se hai bisogno di più dettagli.
Teter28

6
Cosa sono i metadati della classe?
Sebastian,

Questa risposta è pseudo codice e obsoleta. Molti dei metodi non esistono più.
N-ate

9

Esistono un paio di framework che consentono di modificare dinamicamente qualsiasi metodo in fase di esecuzione (utilizzano l'interfaccia ICLRProfiling menzionata da user152949):

Ci sono anche alcuni framework che prendono in giro gli interni di .NET, questi sono probabilmente più fragili e probabilmente non possono cambiare il codice inline, ma d'altra parte sono completamente autonomi e non richiedono l'uso di un lanciatore personalizzato.

  • Harmony : licenza MIT. Sembra essere stato effettivamente utilizzato con successo in alcune mod di gioco, supporta sia .NET che Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 e commerciale. Il supporto .NET attualmente contrassegnato come sperimentale, ma d'altra parte ha il vantaggio di essere supportato commercialmente.

8

La soluzione di Logman , ma con un'interfaccia per lo scambio dei corpi dei metodi. Inoltre, un esempio più semplice.

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

1
Questo mi ha dato: Un'eccezione di tipo "System.AccessViolationException" si è verificata in MA.ELCalc.FunctionalTests.dll ma non è stata gestita nel codice utente Ulteriori informazioni: Tentativo di leggere o scrivere la memoria protetta. Questo è spesso un'indicazione che altra memoria è danneggiata. ,,, Quando si sostituisce un getter.
N-ate

Ho ricevuto l'eccezione "wapMethodBodies non gestisce ancora la dimensione IntPtr di 8"
Phong Dao

6

Sulla base della risposta a questa e a un'altra domanda, ive mi è venuta questa versione riordinata:

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

Per il momento questa è la migliore risposta
Eugene Gorbovoy

sarebbe utile aggiungere un esempio di utilizzo
kofifus


3

So che non è la risposta esatta alla tua domanda, ma il modo solito per farlo è usare l'approccio factory / proxy.

Per prima cosa dichiariamo un tipo di 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;
    }
}

Quindi possiamo dichiarare un tipo derivato (chiamalo 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();

Il tipo derivato può essere generato anche in fase di esecuzione.

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

L'unica perdita di prestazioni è durante la costruzione dell'oggetto derivato, la prima volta è abbastanza lenta perché utilizzerà molto riflesso e riflesso emesso. Tutte le altre volte, è il costo di una ricerca di tabella simultanea e di un costruttore. Come detto, puoi ottimizzare la costruzione usando

ConcurrentDictionary<Type, Func<object>>.

1
Hmm ... che richiede ancora lavoro da parte del programmatore per essere attivamente consapevole dell'elaborazione distribuita; Stavo cercando una soluzione che si basi solo su di loro impostando l'attributo [Distributed] sul metodo (e non sottoclasse o ereditando da ContextBoundObject). Sembra che potrei aver bisogno di apportare alcune modifiche di post-compilazione agli assembly usando Mono.Cecil o qualcosa del genere.
June Rhodes

Non direi che questo è il solito modo. In questo modo è semplice in termini di abilità richieste (non è necessario comprendere CLR), ma richiede di ripetere gli stessi passaggi per ogni metodo / classe sostituito. Se in seguito vuoi cambiare qualcosa (ad esempio, eseguire del codice dopo, non solo prima), dovrai farlo N volte (a differenza del codice non sicuro che richiede di farlo una volta). Quindi è N ore di lavoro contro 1 ora di lavoro)
Eugene Gorbovoy il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.