Thursday, June 26, 2008
==, .Equals() and ReferenceEquals()
C#.NET offers a variety of ways of testing for equality. For the most part, things just work as the programmer would expect. But, because the
For starters, just what does the
For your own classes, you can override the
If the
Note that
Ok, so you can use the
That works, but should you do that? I say "no". Why? The reason is simple: because Visual Studio will underline that code with a green squiggly line, and when you mouse over it the tooltip says "Possible unintended reference comparison; to get a value comparison, cast the left hand side to type 'string'". Basically, they are telling you that something is "fishy" and that means it does not fall under best practices.
By now. You may be asking yourself If the cast-trick above is not best practices, how do I compare two strings to see if they reference the same value? OR, If the
The
There are a few more things to be aware about when it comes to comparisons using the
So now think back to that
That pretty much covers a few of the basics of comparisons in .NET. But, once again, string comparisons prevent a host of problems. Do you want to do a case-sensitive or insensitive comparison? What about cultural based comparisons? To see if you will get burnt by a Turkey, see the blog post from my colleague Jeff Moser in the office next to me.
== operator and Equals() method can be overriden, some guidelines have been established by Microsoft to ensure that things continue to work as expected.For starters, just what does the
== operator do? Well, that depends on what type of data you are dealing. For the predefined value types, the == operator returns true of the values are equal. For predefined reference types (except strings), the == operator returns true if both sides reference the same object. And, the System.String class (a predefined, immutable reference type) overrides the == operator to return true if both sides have the same value.For your own classes, you can override the
== operator to do a value comparison instead of a reference comparison, but that does not mean you should! You should seriously think twice before doing that because most programmers will assume it is still a reference comparison.If the
System.String overrides == to compare string values, just what does it mean for two strings to have the same value? The string's == operator actually calls the static String.Equals(string, string) method. And, that method will return true if...- Both parameters are null
- Both parameters reference the same object
- Both parameters are have the same number of characters (i.e. same length) and the ordinal value of each character is the same
Note that
String.Equals() compares the ordinal value. This means that the comparison is inherently case-sensitive and culture-insensitive. That is, the Unicode values of the characters at each position in the two strings must be same.Ok, so you can use the
== operator to compare the value of two strings. But, what if you want actually do want to compare two string variables to see if they reference the same thing? One common "trick" people do is to cast one (or both) of the string variables to an object. Because one side is now an object, the compiler will use System.Object's == operator instead of the System.String operator to do that comparison, and that operator will do a reference comparison.
bool flag = (object)a == b;
That works, but should you do that? I say "no". Why? The reason is simple: because Visual Studio will underline that code with a green squiggly line, and when you mouse over it the tooltip says "Possible unintended reference comparison; to get a value comparison, cast the left hand side to type 'string'". Basically, they are telling you that something is "fishy" and that means it does not fall under best practices.
By now. You may be asking yourself If the cast-trick above is not best practices, how do I compare two strings to see if they reference the same value? OR, If the
== operator may have been overridden to compare values, how do I know I am actually comparing references? Truth-be-told, the answer to both of those questions is the same: use the static System.Object.ReferenceEquals() method instead.The
Object.ReferenceEquals() method unequivocally compares two references to see if they reference to the same instance an object. With normal objects, this is pretty straight-forward to understand. But, once again, things actually get a bit tricky with strings as is shown in the example below.
string a = "12";
string b = "1" + "2";
string c = "1"; c += "2";
// flagA1, flagA2, flagA3 and flagA4 are all true
// because they were all assigned by value comparisons.
bool flagA1 = (a == "12");
bool flagA2 = (a == a);
bool flagA3 = (a == b);
bool flagA4 = (a == c);
// flagB1, flagB2 and flagB3 are all true because the
// compiler interns the variables a & b and literal "12"
// to the same string.
bool flagB1 = object.ReferenceEquals(a, "12");
bool flagB2 = object.ReferenceEquals(a, a);
bool flagB3 = object.ReferenceEquals(a, b);
// flagB4 is false because variable c was created by
// concatenating two values at run-time, and therefore
// is not interned to the same "12" as variables a & b
// literal "12". Sure, variable b was concatenated too,
// but that was at compile time so it was still interned.
bool flagB4 = object.ReferenceEquals(a, c);
There are a few more things to be aware about when it comes to comparisons using the
== operator. Ever here of boxing and unboxing? The == operator will compare the values of two value-type parameters, but boxed parameters are no longer value type parameters!
bool CompareBoxedValues(object a, object b)
{
return (a == b);
}
int a = 1;
int b = 1;
// flag1 is true because the two values are equal
bool flag1 = (a == b);
// flag2 is false because the two variables are boxed
// into different objects
bool flag2 = CompareBoxedValues(a, b);
// flag3 is also false because the same variable is
// boxed into two different objects
bool flag3 = CompareBoxedValues(a, a);
So now think back to that
Object.ReferenceEquals() method. What do you think happens if you pass non-references (i.e. values) into it? Sure enough, each value gets boxed into a different object instance so the method always returns false!
// The following always sets flag to false
int a = 1;
bool flag = ReferenceEquals(a, a);
That pretty much covers a few of the basics of comparisons in .NET. But, once again, string comparisons prevent a host of problems. Do you want to do a case-sensitive or insensitive comparison? What about cultural based comparisons? To see if you will get burnt by a Turkey, see the blog post from my colleague Jeff Moser in the office next to me.
Monday, June 23, 2008
Statics and Generics
If you think about it, it makes total sense. But, this is something you need to be careful of if you are use static member variables inside of generic classes. Basically, each different type you pass into a generic creates a new class, and each of those classes has its own set of static member variables!
As an example, consider the following trivial example class.
Now, consider what happens to the Id property when you create the following instances.
The actual values of the Id properties for those objects are.
The reason this occurs is because
As an example, consider the following trivial example class.
class MyGenericClass<T>
{
private static int s_IdCounter = 0;
private int _id;
private T _value;
public MyGenericClass(T value)
{
_id = Interlocked.Increment(ref s_IdCounter);
_value = T;
}
public int Id
{
get
{
return _id;
}
}
public T Value
{
get
{
return _value;
}
}
}
Now, consider what happens to the Id property when you create the following instances.
MyGenericClass<object> obj1 = new MyGenericClass<object>(null);
MyGenericClass<object> obj2 = new MyGenericClass<object>(null);
MyGenericClass<string> obj3 = new MyGenericClass<string>(null);
The actual values of the Id properties for those objects are.
obj1.Id == 1
obj2.Id == 2
obj3.Id == 1
The reason this occurs is because
obj1 and obj2 are of the same type (MyGenericClass<object>) whereas obj3 is actually of a different type (MyGenericClass<string>). If you truely need them to share the same set of static member variables, then you need another class that contains the statics. You can either derive your generic from that other class, or you can have the generic use that other class. I personally prefer the later because I feel that a class intended to only hold static member variables should be a static class, but the compiler will not let you derive a non-static class from a static class.Thursday, June 19, 2008
Mouse Pointers and Cursors
We've all seen it, and we all want to do it. If an application is busy doing something for extended period of time, we want to change the cursor so that the user knows the application is still busy. But, cursor control can be a bit confusing in C#.NET because there are several ways to control the cursor. I'll try to sum how cursors behave for WinForms within this post.
For starters, just to get the terminology straight from a Windows' point of view, the mouse pointer is the location on the screen that changes as the user moves the mouse, and the cursor is the graphical image that is drawn with its hot spot at the mouse pointer.
In .NET, each cursor image is represented by an instance of the
Instances of
In addition to using instances of the
These static properties describing the current cursor are a bit interesting when it comes to threaded applications. No matter what thread you are on, the
Although you can do it without throwing an exception, getting or setting the
Each WinForms control has a
The
Setting the
If you know you are going to execute code that will block the main UI thread for a bit, you could set
If you know you are going to block the main UI thread fo awhile, you could set the
Each WinForms control has a
The
There are a few last subtle point to note...
Unlike the
Also, if
And, using the wait cursor via
Finally, there are even more ways to change the cursor, such as via the
For starters, just to get the terminology straight from a Windows' point of view, the mouse pointer is the location on the screen that changes as the user moves the mouse, and the cursor is the graphical image that is drawn with its hot spot at the mouse pointer.
In .NET, each cursor image is represented by an instance of the
System.Windows.Forms.Cursor class. Instances of the Cursor class can be used to represent the standard Windows cursors, as well as custom-defined cursors (generally compiled into a resource file).Instances of
Cursor that represent the standard Windows cursors are available as static properties of the System.Windows.Forms.Cursors class. For example, Cursors.WaitCursor returns an instance of a Cursor that contains information about how to display the "wait cursor" configured in Windows.In addition to using instances of the
Cursor class to define what different cursors look like, the Cursor class also has several static properties that described the currently active cursor. Most noteably, the Cursor.Current property sets or gets the Cursor instance that is currently active, and Cursor.Position property gets or sets the current location of the mouse pointer.These static properties describing the current cursor are a bit interesting when it comes to threaded applications. No matter what thread you are on, the
Cursor.Position property always gets or sets the Windows mouse pointer position immediately. But, the Cursor.Current property does not behave the same way!Although you can do it without throwing an exception, getting or setting the
Cursor.Current property form any thread other than the main UI thread is somewhat pointless. If you get the Cursor.Current property from a non-UI thread, you will notice that it defaults to Cursors.WaitCursor even if the actual Windows cursor is something else. And, if you set the Cursor.Current property from a non-UI thread the actual Windows cursor will not change. After doing so, however, calling get from a non-UI thread will return that set value.Each WinForms control has a
Cursor property inherited from the System.Windows.Forms.Control class. When the mouse pointer is over a control, the Windows cursor is changed to whatever is specified by the Cursor property of the control, and the Cursor.Current property is changed to return that cursor as well.The
Control.Cursor property is a so-called ambient property. That means a control will use its container's cursor as long as it has not been specifically set on the control itself. For example, if you have a button on a form, then setting the Cursor property of the form (both in Visual Studio and prgrammatically at run-time) will also change the cursor for the button as long as you have not specifically set the button's Cursor property.Setting the
Cursor.Current property from a non-UI thread may not be useful, but setting it from the main UI thread is a way of temporarily overridding the cursors specified by the Cursor properties of all controls in the application (or at least those on the same UI thread). Setting the Cursor.Current property from the main UI thread changes the Windows cursor immediately and application-globally, but only temporarily. Once Cursor.Current is specifically set, it will continue to override the controls' cursors until it is changed to something else, or until the UI thread "catches up" and runs out of messages to process.If you know you are going to execute code that will block the main UI thread for a bit, you could set
Cursor.Current before executing that code and not have to worry about setting it back or changing it on every form. Once the UI starts responding again, the cursor will go back to being whatever is set on the Control.Cursor property of the control the mouse pointer is over.If you know you are going to block the main UI thread fo awhile, you could set the
Cursor.Current to Cursors.WaitCursor, and Windows would display the wait cursor for the entire application until the main UI thread was no longer busy. But, ideally we do not want to block the main thread for extended periods of time because non-responsive, partially-painted applications not only look bad but also make users think something is wrong. So, unless a long-runnning task has to be on the main UI thread (such as when creating a bunch of items for a combo box), most programmers should choose to do long-running tasks on background threads or in dedicated threads so the main UI thread is not blocked. But, in such cases, we cannot use Cursor.Current to display the wait cursor because the cursor would immediately reset to whatever is appropriate for the control the mouse pointer is over because the UI would be responsive. That's where the UseWaitCursor properties come in!Each WinForms control has a
UseWaitCursor property inherited from System.Windows.Forms.Control. When this property is true, the wait cursor is used instead of whatever cursor is specified in the Control.Cursor property whenever the mouse pointer is over the control or any of it's child controls.The
System.Windows.Forms.Application class also has a UseWaitCursor property. Setting Application.UseWaitCursor causes the framework to loop over all of the open forms and set the Control.UseWaitCursor properties on all of those forms.There are a few last subtle point to note...
Unlike the
Cursor.Current property that assumes the main UI thread will be blocked and therefore changes the Windows cursor immediately, setting the Control.UseWaitCursor or Application.UseWaitCursor properties assumes the UI will remain responsive and therefore does not change the Windows cursor immediately but instead processes it as any other property change. Therefore, if you set the UseWaitCursor and then block the UI thread, you may never see it!Also, if
UseWaitCursor is true, then setting the Cursor.Current cannot be used to override the wait cursor. And, using the wait cursor via
UseWaitCursor does not prohibit other mouse events. It is still possible for the user to press buttons, size forms, enter text, etc. even if UseWaitCursor is true.Finally, there are even more ways to change the cursor, such as via the
DragDropEffects! (Perhaps I will blog about how they interact with the cursors mentioned above in the future?)Subscribe to Posts [Atom]