Monday, May 12, 2014

A Very Practical Use of C# Structs

A programming interview question folks will tell you to be prepared for is "when should you use structs" and the general response is "when you want pass by value behaviour". That is, when you want data to be passed as a copy rather than as a reference.

Ok, but when would you ever want that behaviour? An issue at work recently provided an example: when you can't trust that the thing your object was passed won't be changed. Or that the object you passed in turns out to be the same one passed out later. Here's an example:

There is a class, PersonUsingClasses, that takes items from two data sources and holds on to the references, rather than making copies of all of the properties of each. It then uses those references to construct a new property, DisplayName. I've written such code myself, unaware of the dangers. This class also has a way to convert it back to one of the data source types, ToServicePerson.

A couple of things to notice:
  1. The PersonClassFromService reference sent into the constructor is the same one passed out in ToServicePerson
  2. Someone forgot to remove his debugging code in the constructor.
This means that any changes made to the Service person object inside or outside of the PersonUsingClasses object will appear everywhere at once. Frank changed his salutation in Main to Dr. from outside the class, but the class reflected this change. Also, some code in Main tries to detect if the Age property changed by comparing the input and output objects, and what do you know, there was no change.

One option to fix this is to not hold on to the references passed into the constructor and make copies of every property that matters to PersonClassUsingClasses. That might be a good option, but we need to support recreating the PersonClassFromService object, which could have properties that don't matter. Or, there could be dozens and dozens of properties to track. (I worked at a bank - trust me when I say this happens.)

A nearly equivalent approach is to use return structs from the DB and service:

Now the system copies every property in every input parameter for us because they are structs. It copies every property again when we call ToServicePerson. This means that changing the input parameter does not get reflected in PersonUsingStructs and we can detect the accidental change in the constructor. This approach uses more memory because of all of this copying, but it's not much more than if we'd made copies of every property ourselves in PersonUsingClasses. We've protected the system from accidental changes.

No comments:

Post a Comment