Welcome to MSDN Blogs Sign in | Join | Help

My last post was many months ago, and I've been pretty busy here since then. Nobody who starts a blog means to neglect it, even though mostly that's what happens. I won't let it go for so long again. =)

Anyway, we were talking about events. Field-like events in particular, and the nastiness of their implementation with respect to the way they use synchronization. Sam, also on my team, has a post that has to do with field-like events, in particular that when you declare them virtual and then override them, you should be a bit careful.

I want to bring up something else about events, which is their performance in space--how much of it they use. On a per-instance basis, each field-like event you declare takes up at least the size of a delegate field. Is that the best you can do?

WinForms doesn't think so. Take a minute and reflect over System.Windows.Forms.Control and look at the events. There are 68 of them. I won't list them all here, but they're things like "Click," "GotFocus," "KeyDown," etc. Now, Control is the fundamental base class for pieces of WinForms UI, and so there are likely to be a lot of instances around. If these events were all field-like, then every piece of UI would be bloated by 68 delegate fields. That'll add up quickly, but the important bit is that it's almost all a waste. It's not the case that control events are frequently subscribed to. Mostly they're not. You create a button and then you listen for Click and maybe one or two others, but you're not adding 68 events on the thing.

So what did they do? Well, you can look at the events. They implemented their own accessors, and those accessors, instead of dealing in terms of delegate fields, deal with a list of delegates that have been used so far in that instance.

This strategy allows the list to be sparse.

See, if no one ever calls add for a particular event, then it never gets a list entry. If someone does, then it has a list entry and the total footprint grows by that amount for your instance, but that's ok. You're paying for what you're using!

This is a really good idea, if you're in the same boat. Of course... you're probably not. Just having a handful of events won't warrant this amount of attention, since the container is going to have overhead. And if you implement a lot of events but they're always used, or if you don't have a lot of instances, you probably also don't want to do this. Oh, and if you're going to try an optimization like this, test it to see if it gets you anything. You don't want to wind up with an implementation that's worse than you started with (which is actually not that unlikely--an obvious implementation here uses a Dictionary, but Dictionaries have quite a lot of overhead). You can see that the particular implementation used by System.Windows.Forms.Control has a tiny, hand-crafted list.

So that's another reason sometimes not to use field-like events.

0 Comments
Filed under: ,

Hi all, my name's Chris Burrows, and I'm a developer on the C# Compiler team here in Redmond. I'll introduce myself further in a subsequent post, but I just want to start off with a note that has at least a little substance.

So let's talk about C# events as they've existed for a long time now, and their implementation in the compiler. Last week, I was responding to an issue involving events, I was surprised to discover that the language spec tries to make some guarantee that field-like events are, as it says, "thread safe." When I say "field-like events," I mean events for which you have not defined your own add and remove accessors. Let's have a look at the C# 3.0 specification (10.8.1):

In order to be thread-safe, the addition or removal operations are done while holding the lock (8.12) on the containing object for an instance event, or the type object (7.5.10.6) for a static event.

Thus, an instance event declaration of the form:

class X
{
    public event D Ev;
}

could be compiled to something equivalent to:

class X
{
    private D __Ev; // field to hold the delegate
    public event D Ev
    {
        add
        {
            lock (this) { __Ev = __Ev + value; }
        }

        remove
        {
            lock (this) { __Ev = __Ev - value; }
        }
    }
}

Ok. Let's start at the beginning. You should be wary of the language here that implies that the implementation of events is "thread safe." Thread safety, if you care about it at all for this event, is still something that you need to worry about on your own. What happens if you have some other method in the class that adds or removes delegates from the event field? Are those in a context where you've gotten a lock on the proper object? How about the invocations of that delegate? And what if thread safety isn't even a concern for you in this case? If so, it's a little weird that you have to pay for it.

There are bigger concerns though--in a sense I suppose it's kind of obvious that if you want thread safety you're going to have to worry about it in the rest of your code. But look at the lock. It uses the containing object's sync block! You're never supposed to lock(this)! It's one of those things on .NET style lists marked "never do." The big issue here is that the containing object's sync block is essentially public. If anyone else happens to use it elsewhere (for instance, your class's consumers), then you've set yourself up for threading problems.

And it gets worse. If you read on in the spec, you'll see that if we had declared that event static, then the lock would not have been on this, because there is no this. It would have been on X's type:

class X
{
    private static D __Ev;  // field to hold the delegate
    public static event D Ev
    {
        add
        {
            lock (typeof(X)) { __Ev = __Ev + value; }
        }
        remove
        {
            lock (typeof(X)) { __Ev = __Ev - value; }
        }
    }
}

This has the potential to be a huge problem! It's got all the issues associated with lock(this), except that the type object can be loaded in a domain-neutral context, so that it's shared across all app domains. There, now you've just busted through the app domain isolation and you can cause issues across your entire process for anyone using this event. Potentially.

But let's back up a second. The language in the spec is a little weasely. It says that these forms "could be compiled to something equivalent to..." What does that mean? In fact, in the most up-to-date C# compiler (as well as previous versions), the code is compiled to something equivalent to these add/remove blocks, but only in the case where X is a reference type. If we look at the IL for X.Ev.add, it does not contain the lock or any mention of the Monitor class, but the method's metadata looks like this:

.method public hidebysig specialname instance void
    add_Ev(class D 'value') cil managed synchronized
    { ... }

See that "synchronized?" That tells the runtime to wrap the method in the equivalent of the two lock statements that the spec mentions (depending on whether the method is instance or static). If you want to accomplish the same thing explicitly (which maybe you don't), you would get System.Runtime.CompilerServices in scope and decorate the method with:

[MethodImpl(MethodImplOptions.Synchronized)]

The compiler only inserts this for events on reference types because if you've defined the same event on a value type, it can't work--the sync block will be different for each boxing of the value.

So if we're telling people never to use lock(this), and we're telling people really never ever pretty please don't use lock(typeof(X)), then what should we say about field-like events and whether you should use them? Well, they are an awfully convenient syntax for something that would ordinarily be a lot of boilerplate code, which is evidently a good thing if we're to judge by the introduction of automatic properties in C# 3.0. Events do seem to be a likely surface area for inter-thread communication. Perhaps that explains why the original language designers settled on this behavior so many years ago. I wasn't on the team at the time, so I can't say.

The title of this post is a joke. I'm not telling you not to use field-like events. However...

If I were writing C# code, and I were a stickler for style and correctness, I would define my own event accessors. There are other reasons to do so, but I'll leave myself something else to talk about in a future post. If I knew about this behavior and didn't care, I wouldn't change anything. And in the meantime, you can make your own decisions about your own code using whatever unique requirements you have for it.

0 Comments
Filed under: , ,
 
Page view tracker