Delegates, events, and garbage

Originally posted to Shawn Hargreaves Blog on MSDN, Monday, July 9, 2007

Some commenters on my previous post asked for more information about the situations where delegates allocate memory. Here's the scoop.

There are actually three reasons why careless use of delegates or events can cause memory allocations:

 

Allocating delegate instances

Consider this test class:

    class DelegateTest
    {
        void TestMethod()
        {
            PassIntegerToDelegate(PrintInteger);
        }

        void PrintInteger(int value)
        {
            Trace.WriteLine(value);
        }

        void PassIntegerToDelegate(Action<int> delegateInstance)
        {
            delegateInstance(23);
        }
    }

No allocations there, right?

Bzzzt! Wrong.

Loading the compiled test assembly into Reflector (which, by the way, is a truly invaluable tool for .NET development. If you don't already have it, go download it straight away!) shows what the compiler did with our TestMethod:

    private void TestMethod()
    {
        this.PassIntegerToDelegate(new Action<int>(this.PrintInteger));
    }

It is actually impossible to construct a new delegate without allocating memory, because delegates are reference type objects that live on the heap. Unlike raw function pointers in C, delegates are more than just a pointer to some code. They also store a 'this' reference, providing context for how that code should be invoked. That is why you can create delegates from instance methods, unlike C++ where you can only obtain function pointers to static methods. Some heap space is required to store the association between method and 'this' instance.

The compiler (as of C# 2.0, anyway: you used to have to do this by hand) hides the memory allocation via syntactic sugar that automatically constructs delegates whenever you pass a raw method somewhere a delegate is expected, so you rarely need an explicit "new" statement in your code, but the allocation is still there behind the scenes.

One technique for avoiding garbage is to manually cache the delegate instance so you only have to create it once. This can help if you have a method that will be called many times, and will use the same delegate binding each time:

    class DelegateTest
    {
        Action<int> cachedDelegate;

        void TestMethod()
        {
            if (cachedDelegate == null)
                cachedDelegate = PrintInteger;

            PassIntegerToDelegate(cachedDelegate);
        }
        ...

Events are actually just syntactic sugar over the top of delegates, so they have the same allocation behavior. This test class:

    class EventTest
    {
        event EventHandler MyEvent;

        void SubscribeToEvent()
        {
            MyEvent += MyEventHandler;
        }

        void MyEventHandler(object sender, EventArgs e)
        {
        }
    }

Produces this when examined in Reflector:

    private void SubscribeToEvent()
    {
        this.MyEvent = (EventHandler) Delegate.Combine(this.MyEvent, new EventHandler(this.MyEventHandler));
    }

I am not aware of any way to subscribe to an event without causing memory allocations. Raising events does not necessarily allocate, though, so you'll be fine as long as you can hook everything up ahead of time while loading your levels.

 

Allocating lexical closures

If you are a fan of functional programming like me, you will love the anonymous delegate syntax that was introduced in C# 2.0. It is getting even nicer in C# 3.0. Hurrah!

Unfortunately, however, anonymous delegates can be a major cause of hidden allocations. Compile this test class:

    class ClosureTest
    {
        void TestMethod(int valueOffset)
        {
            PassIntegerToDelegate(delegate(int value)
            {
                Trace.WriteLine(value + valueOffset);
            });
        }

        void PassIntegerToDelegate(Action<int> delegateInstance)
        {
            delegateInstance(23);
        }
    }

And look at the output in Reflector:

    internal class ClosureTest
    {
        [CompilerGenerated]
        private sealed class <>c__DisplayClass1
        {
            public int valueOffset;

            public void <TestMethod>b__0(int value)
            {
                Trace.WriteLine(value + this.valueOffset);
        }
        private void TestMethod(int valueOffset)
        {
            <>c__DisplayClass1 <>8__locals2 = new <>c__DisplayClass1();
            <>8__locals2.valueOffset = valueOffset;
            this.PassIntegerToDelegate(new Action<int>(<>8__locals2.<TestMethod>b__0));
        }
        private void PassIntegerToDelegate(Action<int> delegateInstance)
        {
            delegateInstance(0x17);
        }
    }

In order to preserve our valueOffset parameter so the anonymous delegate can close over it, the compiler has created an internal helper class, allocated on the heap, which can be shared between the TestMethod body and the delegate implementation code. Clever compiler! But not good if we are trying to avoid allocations.

Note that anonymous delegates come in two flavors. Free standing ones (which do not reference any variables from their parent scope) can be implemented as simple static methods, and will not cause any more allocations than any other kind of delegate. It is only when an anonymous delegate is used as a lexical closure that the compiler will generate this extra hidden allocation.

 

Allocating EventArgs instances

The .NET Framework Design Guidelines are very specific about how events should be implemented. Unfortunately this is one of the (thankfully rare) cases where the word of the guidelines is in conflict with the demands of efficiency.

The guidelines say all event handlers should take an argument parameter, which should be of a type derived from System.EventArgs. Trouble is, System.EventArgs is a reference type, so this causes an allocation every time you want to raise an event.

You could keep around a single argument instance, and reuse it from one event to the next. That seems a little odd, though. What if the event handler stores the argument instance somewhere, and wants to come back and look at it later? That seems like a reasonable thing for them to do, but they would get unexpectedly wrong results if you later changed the contents of this instance as part of raising some other unrelated event.

You could just ignore the design guidelines, and make your events without any argument parameter, or perhaps using a value type parameter. Or you could follow the guidelines, and put up with the garbage. Or you could dodge the issue by avoiding events altogether in your main gameplay code.

Blog index   -   Back to my homepage