|
Hi, my name is Jon Person, and I'm addicted to writable properties.
After several years of programming, I had gotten into the habit of making properties writable. It made great sense on the surface -- if the value can be changed then make the property writable and call it easy-to-use. Recently, however, I needed to make use of multithreading more than ever before and I quickly ran into synchronization problems. After several days of re-engineering to solve these problems, however, I'm now convinced that developers should try to make their objects immutable whenever possible to avoid complicated threading issues.
Immutable objects are objects whose properties cannot be modified once the object is created. All property values are instead set via the object's constructors. This approach forbids any "Set" accessors on properties, yet it makes an object completely thread-safe because there are no values to synchronize between threads. Another advantage to immutable objects is that they become simpler than their mutable counterparts. Many times, a mutable object must recalculate other properties within itself to stay in check. The "Angle" class of GPS.NET was a great example of this -- when the "DecimalDegrees" property changed, a lot of code went into recalculating the "Hours," "Minutes," and "Seconds" properties. Immutable objects can shed a lot of this overhead.
I'm now of the opinion that classes should be immutable by design unless a few conditions exist:
- If the class contains several properties, making it immutable may not be a good idea since it would require constructors with a lot of parameters.
- If the class requires significant initialization or finalization, it would not be a good immutable object because these processes would occur frequently whenever a property had to change.
- If the class uses a significant amount of memory, like a huge array, making it immutable might add too much overhead.
- If the class must source events or provide virtual "OnChanged"-type delegates, it cannot be immutable.
- An object should be immutable only if it's properties' types are also immutable. If you can drill-down from your object and eventually a property somehow, the class will not be fully thread-safe! This is why it's best to make your base types immutable first, and build from there.
This looks like a lot to sacrifice, but in my opinion the end result was still worth the effort to gain verifiably thread-safe code for downstream developers. The events were the only painful thing to remove because I had been relying on some of these events to tell a collection that its contents had changed. This, too, however, turned out to be alright to deal with, though it required some extra "IndexOf" code to find the index of an object before replacing it. Additionally, the new immutable classes got rid of some issues with "GetHashCode." This method is supposed to return the same value throughout the lifetime of the class -- even if the class is mutable. The newly-immutable classes now behaved with this rule.
The re-engineering process for each class broke down into several steps:
- Having NUnit tests to test property behavior.
- The return types for each property were verified to be immutable.
- "Set" accessors for the class were removed.
- Code from "Set" accessors were moved into constructors.
- Methods which modified the class had their return value changed to return a new instance of the class instead.
- All events and "OnChanged"-like virtual methods were commented out
- NUnit tests were re-run
After a few days of re-engineering, the GPS.NET 2.0 library is now mostly thread-safe by design and easier to maintain, thanks to immutable objects.
|
|
|
|
|
Filling out forms on the web is one thing that makes me crave having a microchip in my forehead. I've been on three web sites this morning which have each required that I fill out a form. Thanks to the Google Toolbar "AutoFill" feature, this is really easy to do. However, in each of these forms, there were validation errors. One in particular made my blood boil for a few seconds:
ERROR: Please enter an area code without parenthesis.
Call me crazy, but wouldn't it have been easier to add code to remove the parenthesis than it would have been to raise the error?
AreaCode = AreaCodeTextBox.Text.Replace("(", "").Replace(")", "");
if(AreaCode.Text.Length != 3)
MessageBox.Show("ERROR: Please enter an area code without parenthesis.");
It seems that there are web developers out there (myself not immune) who are demanding that users type in absolutely perfect form data before they are allowed to proceed. This is bad practice and leads to a poor user experience because of human error. Phone numbers, social security numbers, names... all of these fields can be typed in different formats yet have the same meaning. Many times, a little bit of string parsing goes a long way. So, for myself and others, here's a friendly reminder:
Make a reasonable effort to anticipate varying data formats. This frequently involves the use of the Replace() and Trim() functions to reduce data to its simplest format. An error should be displayed to the user only if this reasonable effort fails.
... this rule is especially important in online order forms, where "closing the sale" as they say can make or break your company. I was once buying some software, and the order form gave me this astounding error:
ERROR: Credit card numbers may not contain spaces.
What on Earth? A sale brought to its knees by spaces? Once again, the code to fix the problem would have been less code than raising the error!
CreditCardNumber.Replace(" ", "") // ahem
I went back and completed the order, but I swear if it would have given me one more dumb error like that, I would have given up, and they would have missed out on the revenue. It's really just good sense to anticipate human errors in your user interfaces.
If you really want to get serious about this in terms of component, you might consider following the .NET framework technique of adding a static (Shared in Visual Basic) "Parse" method to your classes if they resemble simple value types. Your Parse method will be especially useful if it can handle whatever "ToString" can spit out. I've received a lot of great feedback from GPS.NET customers because the library uses this exact design -- Parse and ToString are designed to handle each other's output. This makes forms and other forms of I/O elegant:
// Make an instance of an object (in this case, an Azimuth)
Azimuth MyAzimuth = Azimuth.Empty;
// Use ToString to show the azimuth in a human-readable form
MyTextBox.Text = MyAzimuth.ToString();
// The user makes modifications, which must be interpreted
MyAzimuth = Azimuth.Parse(MyTextBox.Text);
public static Azimuth Parse(string value) {} // etc.
... if a user types an azimuth of "45.0" or " 45 " or "NE" or " NoRtHeAsT " in a TextBox, it all translates to the simplest meaning in the Parse method (a double of 45.0), and there's no form error. So, remember to anticipate human errors, reduce information to its most basic form when you're collecting it from forms, and consider incorporating "Parse" into your simple value types. This may net you some well-earned revenue as well as thanks from your users by sparing them from string parsing. My apologies for saying "string parsing" -- I will go wash my hands now.
|
|
|
|
|
I completely agree with you here! Particularly on the "closing the sale" bit. My street address has a "/" (slash) in it. The post office agrees that it should have a slash in it, and yet there commerce sites out there that are convinced that "/" is not a valid character in a street address. It gets pretty aggrevating sometimes.
BTW, great GPS related articles!
|
|
|
|
|
Overloading the Equals method in reference types is one of the most confusing guidelines to me because there is no concrete "when to do it" rule for reference types, and there are contradictions that come up when I try to do it. For example:
* When overriding Equals in a reference type to compare values, you should make the equality operator (==) do the same thing.
* Most reference types, even those that implement the Equals method, should not override ==.
So, once you finish implementing Equals, also implement ==, then comment out the code for == because it's really not allowed :P. I think that the two rules above should be rephrased into the following:
"Equals and the equality operator must be synonymous in value types, but have separate meanings in reference types. Override Equals in reference types to compare values only if sufficiently documented and the equality operator is left alone."
I've wrestled with this a lot with the "Angle" class in GPS.NET (http://www.gpsdotnet.com/Support/Documentation/StormSource.Gps.Angle.html) because it is by all accounts a value type. It stores a single value, a double, between 0 and 360 degrees, and is immutable, making it a great candidate as a value type. The only reason it's *not* is that it must be inherited from, and value types cannot be inherited from.
Turning a value type into a base type is a disappointing process because of the sacrifices that must be made. First, since (==) can no longer be overloaded to compare values, all math operators (+, -, +, /) must also be abandoned, which, according to FxCop, requires that the (==) operator be overloaded to test for value. See what I mean? Ouch. Goodbye elegance. Still, I can't help being tempted to break this rule and implement math operators. For example, this looks perfectly fine to me:
Angle Angle1 = new Angle(45);
Angle Angle2 = new Angle(180);
Angle Sum = Angle1 + Angle2; // 180 + 45
... yet it is not allowed!
After a lot of waffling, the Angle class now has what may be the best FxCop-compliant compromise. Mathematical operators are not overloaded (which makes FxCop happy), but mathematical methods "Add," "Subtract," which typically accompany overloaded math operators, are present, allowing for still-readable code:
Angle Angle1 = new Angle(45);
Angle Angle2 = new Angle(180);
Angle Result = Angle1.Add(Angle2); // 180 + 45
... which I guess is reasonable. It's just that Rule Number One of successful component development, in my opinion, is to "Facilitate Efficiency With the Simplest Possible Solution for The Developer, Not the Author." This rule would demand that "+" is better than "Add" because it saves developers the most time, but it's just not allowed, even when a base class is 99.9% similar to a value type.
If I was working on a private project, I would probably ignore FxCop and go ahead with overloading mathematical operators on a reference type, twisting my mustache as I go, but a reusable component is a much larger audience to appeal to. Has anyone ever overloaded mathematical operators in a reusable reference type with favorable feedback? I'm guessing "No."
|
|
|
|
|
I've been wanting to get involved in blogging for a while because GIS and GPS is so fascinating, and there's a ton to learn and discuss about it. As a component developer, I'm also finding that there are a bunch of nuances that come up during .NET development and it's always valuable to get feedback from others before putting something into production. It'll take me a little while to get the hang of this but I'm hoping it becomes a good learning source for GIS, GPS and .NET.
Just a little background about me: I am the author of a GPS product named "GPS.NET" (www.gpsdotnet.com) which has become quite successful as a tool for GPS developers. I am practically addicted to authoring components and have much more fun writing them than end-user applications. The thought of empowering others is more attractive that filling a niche, perhaps. I enjoy writing articles and helping other developers, and have been named a "Most Helpful Member of 2004" on CodeProject (http://www.codeproject.com/script/rumor/article.asp?id=316) for my series on GPS applications.
When I'm not coding, I enjoy spending time with my girlfriend, Bonnie, near our home in the foothills of Colorado. I really like storm chasing as well (hence the blog name), and have been on national TV a few times (a la Real TV, Extreme Driving Quiz, Worst Case Scenario) from my experiences close to storms. Lately, however, programming has become more exciting than storm chasing, believe it or not, because chasing storms requires a ton of driving and there's no guarantee of seeing an exiting storm.
I hope you find this blog useful, and welcome discussions on any of these topics...
|
|
|
|
|
Hi Jon!
You made a great explanation to make me understand how to create some flat maps. But what about rotating them how can we do it? So the data that i have is the radius of a territory. And suppose I'm in the center of this circle. Now the North should correspond with the North on the map, right? So I move a little bit and what my map should make is to move to the position where I am now + the direction of my moving should now correspond to the North(i mean on the axes). So i found a formula of calculating the angle having 2 points. The question is how do i get it rotating. I suppose I should use Matrix.Rotate(), but I'm not sure how to do it.. Can u give me a piece of advice?
|
|
|
|
|
Yes, you would call the "RotateAt" method of the "Matrix" class to apply rotation to your map. Rotation is typically applied after scaling and translation. I unfortunately don't have enough time to teach you how to use the Matrix class, but that's the right direction for you to research Best of luck!
|
|
|
|
|
Jon, could you please provide a piece of code that will rotate correctly your Nebraska Map?
This is what I was trying to do.
transform.Reset();
// First, translate all projected points so that they match up with pixel 0,0
transform.Translate(-adjustedViewport.X, adjustedViewport.Y, MatrixOrder.Append);
// Next, scale all points so that the viewport fits inside the form.
transform.Scale(this.Width / adjustedViewport.Width,
this.Height / -adjustedViewport.Height, MatrixOrder.Append);
transform.Rotate(90.0f);
// Apply this transform to all graphics operations
e.Graphics.Transform = transform;
#endregion
#region Draw a Geographic Shape
// Now draw nebraska using a green interior and black outline
e.Graphics.FillPolygon(brush, projectedCoordinates);
e.Graphics.DrawPolygon(pen, projectedCoordinates);
But this doesn't work. You said that Rotation should be made after Translating and Scaling. Ok after this I thought that the Graphics should be rotated and wrote this:
// Now draw nebraska using a green interior and black outline
e.Graphics.FillPolygon(brush, projectedCoordinates);
e.Graphics.DrawPolygon(pen, projectedCoordinates);
e.RotateTransform(90.0f);
But this doesn't do it. Can you pls give me the exact thing needed here?
|
|
|
|
|
Thank you Jon, enjoyed the article. As someone who has written assembly flight simulators and my own utility for viewing GPS tracks in 3-D (think of an aerial view of a roller coaster) with drag-frame analysis, I was immediately struck by "gee what's the software evolution done for me that I used to have to do by hand".
Came across the rotation problem. I'm nearly certain we could use .Net translation to save part of this but I just got it working by brute force, it's running on my machine right now.
First off, remove the hard-coded initialization of the viewport coordinates;
RectangleF viewport; //removed hard-coded viewport
Then create three PointF's min,max,center and initialized the min and max's to huge numbers.
private PointF Min = new PointF(1E30f,1E30f);
private PointF Max = new PointF(-1E30f,-1E30f);
private PointF Center = new PointF();
Add a variable angle. With no interface to change it yet, this one is still hard-coded to 10 degrees.
private float RotationAngle = 10.0f;
After the population of a point in the projectedCoordinate array compare that point to the min and max and updated as needed. Below is the entire loop for initialization of the projectedCoordinate array with 4 lines added to the prior routine (sorry if I missed some of the html conversion of the paste).
// For each geographic coordinate, project it
for (int index = 0; index < projectedCoordinates.Length; index++)
{
// Convert the geographic coordinate to a projected coordinate
projectedCoordinates[index] = plateCaree.Project(geographicCoordinates[index]);
if (Min.X > projectedCoordinates[index].X) Min.X = projectedCoordinates[index].X;
if (Min.Y > projectedCoordinates[index].Y) Min.Y = projectedCoordinates[index].Y;
if (Max.X < projectedCoordinates[index].X) Max.X = projectedCoordinates[index].X;
if (Max.Y < projectedCoordinates[index].Y) Max.Y = projectedCoordinates[index].Y;
}
Here I'm getting the center of the data that has been converted to projection coordinates
// get the center point of the projection data
Center.X = (Max.X + Min.X) / 2.0f;
Center.Y = (Max.Y + Min.Y) / 2.0f;
To understand what is being done. Imagine throwing down a piece of paper 100 feet by 100 feet and driving a stake in its center. Then walk to one of the edges and draw a picture of Nebraska. Now put a magnifying glass over the picture and spin the sheet. The sheet center was 50 feet away and Nebraska simply spun away from the viewport. This code fixes that.
// translate the projection data so that (0,0) is the middle of the data.
for (int index = 0; index < projectedCoordinates.Length; index++)
{
// Center the projection data
projectedCoordinates[index].X -= Center.X;
projectedCoordinates[index].Y -= Center.Y;
}
Now make a viewport who's center is 0,0 and extends to the width of the data
// make the viewport extend to the boundaries of the projection data
viewport = RectangleF.FromLTRB(Min.X - Center.X, Min.Y - Center.Y, Max.X - Center.X, Max.Y - Center.Y);
---
done.. Now to rotate is a one-liner (whew... finally a ONE-LINER). After the command "translate.Reset();" add this
transform.Rotate(RotationAngle); //some interface to change this is needed yet
10.0f is hard-coded, I'm going to add mouse-drag control to change it but you can set 10.0f to anything you like and add any interface to adjust it that suites you.
|
|
|
|
|