Maison > développement back-end > C++ > Comment puis-je créer une méthode FromEvent à usage général pour TaskCompletionSource en C# ?

Comment puis-je créer une méthode FromEvent à usage général pour TaskCompletionSource en C# ?

Mary-Kate Olsen
Libérer: 2024-12-25 10:10:11
original
943 Les gens l'ont consulté

How Can I Create a General-Purpose FromEvent Method for TaskCompletionSource in C#?

Méthode FromEvent à usage général

Problème :

Création d'une TaskCompletionSource qui se termine lorsqu'un événement les incendies nécessitent une méthode FromEvent personnalisée pour chaque événement de chaque classe. Cela devient fastidieux et répétitif.

Solution souhaitée :

Une méthode FromEvent à usage général qui peut gérer n'importe quel événement sur n'importe quelle instance.

Difficultés :

  • Les événements ne peuvent être rattachés qu'au côté gauche d'une expression.
  • Les types de délégués et les paramètres des événements doivent être déterminés de manière dynamique.
  • Émettre du code IL pour accéder à des instances externes et définir les résultats TaskCompletionSource est un défi.

Solution :

Le code suivant fournit une méthode FromEvent à usage général qui surmonte ces difficultés :

internal class TaskCompletionSourceHolder
{
    private readonly TaskCompletionSource<object[]> m_tcs;

    internal object Target { get; set; }
    internal EventInfo EventInfo { get; set; }
    internal Delegate Delegate { get; set; }

    internal TaskCompletionSourceHolder(TaskCompletionSource<object[]> tsc)
    {
        m_tcs = tsc;
    }

    private void SetResult(params object[] args)
    {
        // this method will be called from emitted IL
        // so we can set result here, unsubscribe from the event
        // or do whatever we want.

        // object[] args will contain arguments
        // passed to the event handler
        m_tcs.SetResult(args);
        EventInfo.RemoveEventHandler(Target, Delegate);
    }
}

public static class ExtensionMethods
{
    private static Dictionary<Type, DynamicMethod> s_emittedHandlers =
        new Dictionary<Type, DynamicMethod>();

    private static void GetDelegateParameterAndReturnTypes(Type delegateType,
        out List<Type> parameterTypes, out Type returnType)
    {
        if (delegateType.BaseType != typeof(MulticastDelegate))
            throw new ArgumentException("delegateType is not a delegate");

        MethodInfo invoke = delegateType.GetMethod("Invoke");
        if (invoke == null)
            throw new ArgumentException("delegateType is not a delegate.");

        ParameterInfo[] parameters = invoke.GetParameters();
        parameterTypes = new List<Type>(parameters.Length);
        for (int i = 0; i < parameters.Length; i++)
            parameterTypes.Add(parameters[i].ParameterType);

        returnType = invoke.ReturnType;
    }

    public static Task<object[]> FromEvent<T>(this T obj, string eventName)
    {
        var tcs = new TaskCompletionSource<object[]>();
        var tcsh = new TaskCompletionSourceHolder(tcs);

        EventInfo eventInfo = obj.GetType().GetEvent(eventName);
        Type eventDelegateType = eventInfo.EventHandlerType;

        DynamicMethod handler;
        if (!s_emittedHandlers.TryGetValue(eventDelegateType, out handler))
        {
            Type returnType;
            List<Type> parameterTypes;
            GetDelegateParameterAndReturnTypes(eventDelegateType,
                out parameterTypes, out returnType);

            if (returnType != typeof(void))
                throw new NotSupportedException();

            Type tcshType = tcsh.GetType();
            MethodInfo setResultMethodInfo = tcshType.GetMethod(
                "SetResult", BindingFlags.NonPublic | BindingFlags.Instance);

            // I'm going to create an instance-like method
            // so, first argument must an instance itself
            // i.e. TaskCompletionSourceHolder *this*
            parameterTypes.Insert(0, tcshType);
            Type[] parameterTypesAr = parameterTypes.ToArray();

            handler = new DynamicMethod("unnamed",
                returnType, parameterTypesAr, tcshType);

            ILGenerator ilgen = handler.GetILGenerator();

            // declare local variable of type object[]
            LocalBuilder arr = ilgen.DeclareLocal(typeof(object[]));
            // push array's size onto the stack 
            ilgen.Emit(OpCodes.Ldc_I4, parameterTypesAr.Length - 1);
            // create an object array of the given size
            ilgen.Emit(OpCodes.Newarr, typeof(object));
            // and store it in the local variable
            ilgen.Emit(OpCodes.Stloc, arr);

            // iterate thru all arguments except the zero one (i.e. *this*)
            // and store them to the array
            for (int i = 1; i < parameterTypesAr.Length; i++)
            {
                // push the array onto the stack
                ilgen.Emit(OpCodes.Ldloc, arr);
                // push the argument's index onto the stack
                ilgen.Emit(OpCodes.Ldc_I4, i - 1);
                // push the argument onto the stack
                ilgen.Emit(OpCodes.Ldarg, i);

                // check if it is of a value type
                // and perform boxing if necessary
                if (parameterTypesAr[i].IsValueType)
                    ilgen.Emit(OpCodes.Box, parameterTypesAr[i]);

                // store the value to the argument's array
                ilgen.Emit(OpCodes.Stelem, typeof(object));
            }

            // load zero-argument (i.e. *this*) onto the stack
            ilgen.Emit(OpCodes.Ldarg_0);
            // load the array onto the stack
            ilgen.Emit(OpCodes.Ldloc, arr);
            // call this.SetResult(arr);
            ilgen.Emit(OpCodes.Call, setResultMethodInfo);
            // and return
            ilgen.Emit(OpCodes.Ret);

            s_emittedHandlers.Add(eventDelegateType, handler);
        }

        Delegate dEmitted = handler.CreateDelegate(eventDelegateType, tcsh);
        tcsh.Target = obj;
        tcsh.EventInfo = eventInfo;
        tcsh.Delegate = dEmitted;

        eventInfo.AddEventHandler(obj, dEmitted);
        return tcs.Task;
    }
}
Copier après la connexion

Avantages de cette solution :

  • Prend en charge tout type de type de délégué, éliminant le besoin de spécifier des types spécifiques.
  • Émet du code IL au moment de l'exécution pour gérer l'invocation des délégués et le paramètre de résultat TaskCompletionSource, offrant ainsi flexibilité et performances.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal