Sign in to follow this  
  • entries
  • comments
  • views

Rule of Programming #8

Sign in to follow this  


I have this little rule that I often use when programming. The rule states "If something seems like it's too complex, then there's probably a better way to do it."

Indeed, this rule has served me well over the years. Most of the time, when I find myself doing something that feels like there should be a better way, there actually is a better way.

The reason I say this is because I ran into a heck of a problem yesterday, and with a little guesswork, it turns out I saved myself a nightmare in future maintainence problems.

I have a system set up where there are mutable objects that represent records of a database. When these objects are modified, a graphics control will be notified that a field has changed. While .NET supports this automatically using datasets and datagrids, these controls were not quite what were needed for the project.

Regardless, these objects have a number of events in them, so that they can be hooked up into graphics controls whenever they are updated.

Furthermore, the system uses binary serialization (which is incredibly cool, I'm finding out) to write these objects out to disk and read them back in.

So I decided to write out my objects to disk.

Serialization Exception?

D'oh. I didn't think too far ahead. Serialization does a graph traversal, so it tried serializing all of the graphics controls that are linked up to the objects. No problemmo, just mark the events as nonserializable, and we'll be on our merry way!

Compiler error? Huh?

Says we can't mark Events as non-serializable since they aren't fields. Rather than trying to analyze exactly what this means, I just assumed that this was there for a reason and that non-serializing events is just a feature I'll have to live without. So, I googled the problem, assuming that it's been come across before.

Luckily, it has. Unluckily, the solutions I found on Google were worthy of a dailywtf. Here are some of the solutions that were suggested:

1) use an EventHandlerList to handle events and mark that as non-serialized.

I actually started working on this kind of a solution, but abandoned it quickly. EventHandlerList's hold raw Delegate types, and adding 500 casts to my program is an unoptimal solution. Ie: I can't stand things like this:

(UpdateTransactionDelegate)transaction.Events["AccountNameUpdated"] += new UpdateTransactionDelegate( this.UpdateAccountName );

I mean, wow. So I had the brillant idea of wrapping up an event handler list inside of a generic class that would perform the casts for me! Brillant!!!

Or not. .NET doesn't support using delegates as a template constraint. There's probably a good reason for this, but I was frustrated and decided to move on.

2) Implement ISerializable and override the serialization method.

I vetoed this almost immediately. The whole point of using serialization is that I won't have update a serialization function every freaking time the object changes its data set.

3) Use an inner class to wrap up the events and mark an instance of it as non-serializable.

I actually finished programming this before I realized what I did wrong. I forgot that events are only callable from the class itself. So I had:

transaction.Update.AccountName += new UpdateTransactionDelegate( this.UpdateAccountName );

this.Update.AccountName( this ); // error! cannot call events in other classes! D'oh!

I googled this approach some more, since it seemed to be going down the right path... and found that the guy who suggested it ended up implementeting a wrapper function around each event to call it. OH GOD. No thanks, abort abort abort.

I found a bunch of other kooky solutions as well, but I eventually sat down and said:

Enough! There's got to be a simple way to do this. The .NET designers were very meticulous and everything they do has a purpose, and it's very unlikely that they overlooked something as simple as being able to mark events as non-serialized!

So, I sat down and thought about the problem. Okay, what is an event? It's just a fancy delegate, right? So underneath the hood, they probably turn that event into a delegate field with auto-generated property accessors.

I tested out my theory, and ran a Type.GetFieldInfo() on it. Sure enough:

{FinancialManager.UpdateTransactionDelegate AccountNameUpdated}

How about that... it *is* a field! It's a private delegate field. Ok, so, the system probably generates the delegate field and a few properties around it.

My property theory turned out to be wrong, though. A Search through all the properties showed nothing; but that's ok, because a search through Type.GetEvents() showed the events, so either way, .NET generated some behind-the-scenes stuff.

Now, on to the brillance :)

So, the event cannot be marked as Non-serialized. This makes sense, because the event isn't actually data. But how do I tell .net to non-serialize the underlying delegate? I tried all types of keywords, but eventually, this worked:

[field: NonSerialized]
public event UpdateTransactionDelegate AccountNameUpdated;
[field: NonSerialized]
public event UpdateTransactionDelegate DateUpdated;
[field: NonSerialized]
public event UpdateTransactionDelegate AmountUpdated;
[field: NonSerialized]
public event UpdateTransactionDelegate CategoriesUpdated;
[field: NonSerialized]
public event UpdateTransactionDelegate DescriptionUpdated;
[field: NonSerialized]
public event UpdateTransactionDelegate BalanceUpdated;

I suppose this is telling .NET that the underlying field that is generated by the event will be non-serialized. I looked through MSDN for anything explaining this, but I couldn't find anything, so I'm a little cheesed that this wasn't in the docs anywhere, but I knew something like this HAD to exist. I would have been very surprised if it hadn't.

So now I feel sorry for all those chums who wrote multi-dozen line unmaintainable solutions, when they could have simply added 5 letters and a colon.
Sign in to follow this  


Recommended Comments

perhaps I should adjust my filter settings to include articles.

This still doesn't excuse the fact that it's not in the .NET SDK docs though. I spent well over an hour looking at home last night (where I have no internet access)

Share this comment

Link to comment
That article is wrong, by the way.

The real challenge in type-versioning tolerance is dealing with new members because the old serialization information does not contain any information about them. By default, the binary formatter is not tolerant of the new members, and will throw an exception. The .NET Framework 2.0 addresses this problem by providing the OptionalField field attribute—a simple attribute with a single public member of type int called VersionAdded:

I don't use the OptionalField attribute anywhere, and binary formatter has never had a problem deserializing old data into a new version of the structure that contains new fields.


Share this comment

Link to comment
This is one of those cases where a "smack-MSDN-over-the-internet-USB-periphrical" would be really nice...

Actually a "smack-*-over-the-internet-USB-periphrical" would be really awesome in general. I'll add it to my to-do list.

Share this comment

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now