简体   繁体   中英

What is the complexity of subscribing (+=) and unsubscribing (-=) a delegate in c#?

What is the complexity of subscribing (+=) and unsubscribing (-=) a delegate in c#?

namespace MulticastDelegateDemo
{
    public delegate void MathDelegate(int No1, int No2);

    public class Program
    {
        public static void Add(int x, int y)
        {
            Console.WriteLine("THE SUM IS : " + (x + y));
        }
        public static void Sub(int x, int y)
        {
            Console.WriteLine("THE SUB IS : " + (x - y));
        }
        public void Mul(int x, int y)
        {
            Console.WriteLine("THE MUL IS : " + (x * y));
        }
        public void Div(int x, int y)
        {
            Console.WriteLine("THE DIV IS : " + (x / y));
        }
        
        static void Main(string[] args)
        {
            Program p = new Program();
            MathDelegate del1 = new MathDelegate(Add);
            MathDelegate del2 = new MathDelegate(Program.Sub);
            MathDelegate del3 = p.Mul;
            MathDelegate del4 = new MathDelegate(p.Div); ;

            //In this example del5 is a multicast delegate. We can use +(plus) 
            // operator to chain delegates together and -(minus) operator to remove.
            MathDelegate del5 = del1 + del2 + del3 + del4;
            del5.Invoke(20, 5);
            Console.WriteLine();
            del5 -= del2;
            del5(22, 7);
            
            Console.ReadKey();
        }
    }
}

The implementation will be compiler dependent; however based on the interface, restrictions and use cases it would be easiest and most likely most efficient to implement the backend using linked lists. The main impact would be on unsubscribe lookups.

You can subscribe the same delegate multiple times in Visual Studio 19 (huge performance hit if you leak that dozens of times) so the implementation is obviously just appending what you give it.

Doing a simple loop test subscribe is significantly faster and unsubscribe is kicking the fan in on my laptop

This is using the StopWatch class and ElapsedMilliseconds - The classes are empty outside of the standard event declaration and methods.

Looping 50000 times
Subscribe: 15
Unsubscribe: 9202
        static void Main(string[] args)
        {
            EventSubscirber ms = new EventSubscirber();
            MyEventClass myEventClass = new MyEventClass();

            int loops = 50000;

            Stopwatch swsubscribe = new Stopwatch();
            Stopwatch swunsubscribe = new Stopwatch();

            swsubscribe.Start();
            for (int i = 0; i < loops; i++)
            {
                myEventClass.SampleEvent += ms.SampleEventReceiver;
            }
            swsubscribe.Stop();
            Console.WriteLine($"Looping {loops} times");
            Console.WriteLine($"Subscribe: {swsubscribe.ElapsedMilliseconds}");

            swunsubscribe.Start();
            for (int i = 0; i < loops; i++)
            {
                myEventClass.SampleEvent -= ms.SampleEventReceiver;
            }

            swunsubscribe.Stop();
            Console.WriteLine($"Unsubscribe: {swunsubscribe.ElapsedMilliseconds}");
        }

Just guessing, but based on timing, its iterating the full list each time and unsubscribing the last one that matches.

Subscribing ( += ) and unsubscribing ( -= ) are actually shorthands for System.Delegate.Combine and System.Delegate.Remove static methods invocations. They internally call correspondingly CombineImpl and RemoveImpl for passed delegates pairs (with some exclusions for cases with null parameters passed).

You can check the actual implementation yourself on source.dot.net . There you can see that for MulticastDelegate.CombineImpl complexity is O(m) where m is number of items in the right operand invocation list:

Action act = null; int count = 25000; var sw = new Stopwatch();

sw.Restart();
for (int i = 0; i < count; i++)
{
    Action a = () => Console.WriteLine(); 
    act = (a += act);
}
Console.WriteLine($"Subscribe - {count}: {sw.ElapsedMilliseconds}"); // prints "Subscribe - 25000: 1662" on my machine

sw.Restart();
for (int i = 0; i < count; i++)
{
     act += () => Console.WriteLine();
}
Console.WriteLine($"Subscribe - {count}: {sw.ElapsedMilliseconds}"); // prints "Subscribe - 25000: 2" on my machine

As for MulticastDelegate.RemoveImpl it get's harder to quickly estimate the complexity, but it seems to be O(n) (where n is number of items in the right operand invocation list) for cases where n > m .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM