Notes Chapter 17 Delegation

Keywords: C# Lambda

17.1 Initial Knowledge Entrustment

  • net provides callback function mechanism through delegation.
  • Delegate ensures that callback methods are type-safe.
  • Delegates allow multiple methods to be invoked sequentially.

17.2 Delegated Callback Static Method

  • When binding methods to delegates, both C_ and C LR allow for covariance and contravariance of reference types.
  • Covariance means that a method can return a type derived from the return type of the delegate.
  • Inversion is the base class where the parameters obtained by the method can be delegated parameter types.
  • Covariance and inversion are supported only for reference types, but not for value types or void

    delegate object MyCallback(FileStream s);
    
    //Can be applied to delegate MyCallback
    string SomeMethod(Stream s);
    
    //The return value is a value type and cannot be applied to delegate MyCallback
    int SomeOtherMethod(Stream s)

17.3 Using Delegated Callback Example Method

17.4 Delegation of Secrets

  • When the compiler sees the delegation, it generates a class with the same name and inherits from System.MulticastDelegate
  • System. Multicast Delegate is derived from System.Delegate
  • Three important non-public fields of System.MulticastDelegate:
    • When the delegate object wraps a static method, this field is null. When the delegate object wraps an instance method, this field refers to the object to be operated by the callback method.
    • Method Ptr System. IntPtr An internal integer value that CLR uses to identify the method to be called back.
    • invocationList System.Object This field is usually null, which refers to an array of delegates when constructing a delegate chain.

17.5 Delegate callback multiple methods (delegate chain)

  • Use the Delegate class's common static method Combine to add delegates to the chain:

    fbChain=(Feedback) Delegate.Combine(fbChain, fb1);
  • Delegate is deleted from the chain using the common static method Remove of the Delegate class. Each Remove method call can only delete one delegate from the chain.

    fbChain=(Feedback) Delegate.Combine(fbChain, fb1);
  • Pseudo-code of Invoke that delegates public delegate int Feedback(int value):

    public int Invoke(int value) {
        int result;
        Delegate[] delegateSet = _invocationList as Delegate[];
        if (delegateSet != null)
        {
            // This delegate array specifies the delegate that should be invoked
            foreach (Feedback d in delegateSet)
                result = d(value);  //Call each delegate
        }
        else {  //Otherwise, it's not a chain of delegation.
            //The delegate identifies a single method to be tuned.
            //Call this callback method on the specified target object
            result = _methodPtr.Invoke(_target,value);
            //The above line of code is close to the actual code.
            //What actually happened cannot be expressed in C #.
        }
        return result;
    }

17.5.1 C Support for Delegation Chain

  • The c compiler automatically overloads the += and - = operators for instances of the delegate type. These operators call Delegate.Combine and Delegate.Remove, respectively.

    fbChain += fb1;
    fbChain += fb2;
    fbChain -= fb1;

17.5.2 Gets more control over delegation chain calls

  • The collection of delegates in the delegation chain can be obtained through the instance method GetInvocationList, and then these delegates can be explicitly invoked through a custom algorithm.

17.6 Delegate definitions are not too many (generic delegates)

  • Use Action < T > and Func < T > delegates whenever possible. Except where ref, out, params keywords are needed.

17.7 C Simplified grammar for delegates

  • These are just grammatical sugars for C #.

17.7.1 Simplified Grammar 1: No need to construct delegate objects

  • Since the c compiler can infer by itself, the code for constructing the WaitCallback delegate object in the ThreadPool.QueueUserWorkItem method can be omitted.

    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);
    }
    
    private static void SomeAsyncTask(object o) {
        Console.WriteLine(o);
    }

17.7.2 Simplified Grammar 2: There is no need to define callback methods (lambda expressions)

  • C # allows you to write callback code using Lambda expressions, such as:

    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem(obj =>Console.WriteLine(obj), 5);
    }
  • When the C compiler sees this lambda expression, it automatically defines a new private method in the class, called an anonymous function, as follows:

    class Program
    {
    //The private field is created to cache the delegate object.
    //Advantage: You don't create a new object every time you call it
    //Disadvantage: Cached objects will never be garbage collected
    [CompilerGenerated]
    private static WaitCallback <>9_xxxDelegate1;
    
    static void Main(string[] args)
    {
        if(<>9_xxxDelegate1==null)
            <>9_xxxDelegate1 = new WaitCallback(<Main>.b_0);
    
        ThreadPool.QueueUserWorkItem(<>9_xxxDelegate1,5);
    }
    
    [CompilerGenerated]
    private static void <Main>.b_0(object obj) {
        Console.WriteLine(obj);
    }
    }
  • If the calling method is not static, but its internal anonymous function does not contain instance member references, the compiler will still generate static anonymous functions, because it is more efficient than the instance method; but if the code of the anonymous function does refer to instance members, the compiler will generate non-static anonymous functions.
  • Here are some examples of lambda expressions:

    //If the delegate does not get any parameters, use ()
    Func<string> f = () => "Jeff";
    
    //If the delegate obtains one or more parameters, the type can be explicitly specified
    Func<int, string> f2 = (int n) => n.ToString();
    Func<int, int, string> f3 = (int n1, int n2) => (n1 + n2).ToString();
    
    //If the delegate obtains one or more parameters, the compiler can infer the type
    Func<int, string> f4 = (n) => n.ToString();
    Func<int, int, string> f5 = (n1, n2) => (n1 + n2).ToString();
    
    //If a delegate obtains a parameter, it can omit (and)
    Func<int, string> f6 = n => n.ToString();
    
    //If the delegate has ref/out parameters, you must explicitly specify ref/out and type
    Bar b = (out int n) => n = 5;
    
    //Definition of Bar
    delegate void Bar(out int z);
  • If the body consists of two or more statements, the statement must be enclosed in braces. In the case of braces, if the delegate expects a return value, the return statement must also be added to the body.

17.7.3 Simplified Grammar 3: Local variables can be passed back to callback methods without manual wrapping into classes

Sample code:

    static void Main(string[] args)
    {
        //Some local variables
        int numToDo = 20;
        int[] squares = new int[numToDo];
        AutoResetEvent done = new AutoResetEvent(false);
        //Perform a series of tasks on other threads
        for (int n = 0; n < squares.Length; n++)
        {
            ThreadPool.QueueUserWorkItem(obj =>
            {
                int num = (int)obj;
                //This task is usually more time-consuming
                squares[num] = num * num;
                //If this is the last task, let the main thread continue to run
                if (Interlocked.Decrement(ref numToDo) == 0)
                    done.Set();
            }, n);
        }

        done.WaitOne();

        for (int n = 0; n < squares.Length; n++)
        {
            Console.WriteLine("Index {0} ,Square={1}", n, squares[n]);
        }
    }
  • For the above code, the C compiler defines a new auxiliary class that defines a field for each value that is intended to be passed to the callback code.
  • In addition, the callback code must be defined as an instance method in the auxiliary class.
    The C# compiler rewrites the code as follows:

    class Program
    {
        static void Main(string[] args)
        {
            //Some local variables
            int numToDo = 20;
            WaitCallback callback1 = null;
    
            //Examples of Constructing Auxiliary Classes
            <>c_DisplayClass2 class1 = new <>c_DisplayClass2();
    
            //Initialize fields of auxiliary classes
            class1.numToDo = numToDo;
            class1.squares = new int[class1.numToDo];
            class1.done = new AutoResetEvent(false);
    
            //Perform a series of tasks on other threads
            for (int n = 0; n < class1.squares.Length; n++) {
                if (callback1 == null) {
                    //New Delegate Object Binding to Auxiliary Object and Its Anonymous Instance Method
                    callback1 = new WaitCallback(class1.<Main>b_0);
                }
    
                ThreadPool.QueueUserWorkItem(callback1,n);
            }
    
            //Waiting for all other threads to finish running
            class1.done.WaitOne();
    
            //Display results
            for (int n = 0; n < class1.squares.Length; n++) {
                Console.WriteLine("Index {0} ,Square={1}", n, class1.squares[n]);
            }
        }
    
        //To avoid conflicts, the auxiliary class is given a strange name.
        //Also designated as private, access from outside the Program class is prohibited
        [CompilerGenerated]
        private sealed class <>c_DisplayClass2:Object{
            //Each local variable used by the callback code has a corresponding public field
            public int[] squares;
            public int numToDo;
            public AutoResetEvent done;
    
            //Public parametric constructor
            public <>c_DisplayClass2{}
    
            //Common Instance Method with Callback Code
            public void <Main>b_0(object obj) {
                int num = (int)obj;
                squares[num] = num * num;
                if (Interlocked.Decrement(ref numToDo) == 0)
                    done.Set();
            }
        }
    }
  • The author sets a rule for himself: if you need to include more than three lines of code in the callback method, instead of using lambda expressions, you write a method by hand and assign it your own name.

17.8 Delegation and Reflection

A Delegate object can be created through the CreateDelegate method provided by System.Reflection.MethodInfo, and then the DynamicInvoke method of the object can be invoked to invoke the delegate dynamically at run time.

//Here are some different delegation definitions
internal delegate object TwoInt32s(int n1, int n2);
internal delegate object OneString(String s1);
static class Program
{
    static void Main(string[] args)
    {
        //If delegation is in a namespace, the first parameter must be a fully qualified name with a namespace
        //args = new[] { "TwoInt32s", "Add", "1", "11" };

        if (args.Length < 2)
        {
            Console.WriteLine("Usage: TwoInt32s Add 123 321");
            return;
        }

        //Converting delType arguments to delegate types
        Type delType = Type.GetType(args[0]);
        if (delType == null)
        {
            Console.WriteLine("Invalid delType argument: " + args[0]);
            return;
        }

        Delegate d;
        try
        {
            //Converting Arg1 arguments to methods
            MethodInfo mi = typeof(Program).GetTypeInfo().GetDeclaredMethod(args[1]);
            //Create delegate objects that wrap static methods
            d = mi.CreateDelegate(delType);
        }
        catch (ArgumentException)
        {
            Console.WriteLine("Invalid methodName argument: " + args[1]);
            return;
        }

        //Create an array containing only the parameters to be passed to the method through the delegate object
        object[] callbackArgs = new object[args.Length - 2];

        if (d.GetType() == typeof(TwoInt32s))
        {
            try
            {
                //Converting String-type parameters to Int32-type parameters
                for (int a = 2; a < args.Length; a++)
                    callbackArgs[a - 2] = int.Parse(args[a]);
            }
            catch (FormatException)
            {
                Console.WriteLine("Parameters must be integers.");
                return;
            }
        }
        if (d.GetType() == typeof(OneString))
        {
            //Copy only String parameters
            Array.Copy(args, 2, callbackArgs, 0, callbackArgs.Length);
        }

        try
        {
            //Call the delegate and display the results
            object result = d.DynamicInvoke(callbackArgs);
            Console.WriteLine("Result = " + result);
        }
        catch (TargetParameterCountException)
        {
            Console.WriteLine("Incorrect number of parameters specified.");
        }
    }


    private static object Add(int n1, int n2)
    {
        return n1 + n2;
    }
    private static object Subtract(int n1, int n2)
    {
        return n1 - n2;
    }
    private static object NumChars(string s1)
    {
        return s1.Length;
    }
    private static object Reverse(string s1)
    {
        return new String(s1.Reverse().ToArray());
    }
}

Posted by wednesday on Sat, 01 Jun 2019 13:40:50 -0700