Wednesday, December 30, 2009

Getting Non-Null Data Redux

It has been pointed out to me that my previous post about this topic is a bit out-dated. Yes, it is and I did mention in that post that the code I posted was from an old 1.1 class that I never bothered to update when things like extension methods came along in subsequent .NET versions.

Well, I guess I’ve been chastised enough for it, so I’ll post some new code using extension methods. But first, I do want to say something about extension methods. They are cool, they make some things much easier (as I’ll illustrate at the end of this post) but they can also be over-used and therefore abused (IMHO). When over-using extension methods, it may be very easy to forget that these new methods are not native to .NET, that you (or another developer on your team) has created them. But, I suppose that will be brought home to you when you work on other apps that don’t have the extension method code in them … it just may take you awhile to remember, “Oh yeah, Intellisense isn’t showing this method because it isn’t there natively. Darn!”

One other note about the previously posted code (which I will correct in that post), is that I used code like this:

if (Test != DBNull.Value && Test != null)

When, of course, it should have been the other way around, silly me:

if (Test != null && Test != DBNull.Value)

So, without further ado, here’s the revised version using extension method (and really, all that has to be done is to add “this” to each method signature and I also changed the name of the class):

public static class BBExtensions
{
public static object GetNonNull(this object Test, object Default)
{
if (Test != null && Test != DBNull.Value)
return Test;
else
return Default;
}
public static string GetNonNull(this object Test, string Default)
{
if (Test != null && Test != DBNull.Value)
{
if(Test is DateTime)
{
DateTime TestDT = Convert.ToDateTime(Test);
DateTime SqlNull = new DateTime(1900, 1, 1);

if(TestDT == SqlNull)
return Default;
}
else if (Test is bool)
{
bool YesNo = Convert.ToBoolean(Test);
if (YesNo)
return "Yes";
else
return "No";
}
return Test.ToString().Trim();
}
else
return Default;
}
public static int GetNonNull(this object Test, int Default)
{
if (Test != null && Test != DBNull.Value)
return Convert.ToInt32(Test);
else
return Default;
}
public static DateTime GetNonNull(this object Test, DateTime Default)
{
if (Test != null && Test != DBNull.Value)
{
DateTime TestDT = Convert.ToDateTime(Test);
DateTime SqlNull = new DateTime(1900, 1, 1);
DateTime NetNull = new DateTime(1, 1, 1);
if(TestDT != SqlNull && TestDT != NetNull)
return TestDT;
else
return Default;
}
else
return Default;
}
public static string GetNonNullDate(this object Test, string Default)
{
if (Test != null && Test != DBNull.Value)
{
if(Test is DateTime)
{
DateTime TestDT = Convert.ToDateTime(Test);
DateTime SqlNull = new DateTime(1900, 1, 1);
if(TestDT != SqlNull)
return TestDT.ToShortDateString();
}
return Default;
}
else
return Default;
}
public static DateTime GetNonNullDate(this object Test)
{
if (Test != null && Test != DBNull.Value && Test is DateTime)
return Convert.ToDateTime(Test);
else
return new DateTime(1900, 1, 1);
}
}

So, now why is this better than the old CommonFunctions class I had? Well, here’s how you had to use the old class:

int MyInt = CommonFunctions.GetNonNull(MyDataSet.Tables[0].Rows[0]["MyColumn"], 0);

// -or-

DateTime MyDatetime = CommonFunctions.GetNonNullDate(MyDataSet.Tables[0].Rows[0]["MyDateColumn"])

And here’s the extension method way of doing this, a bit cleaner:

int MyInt = MyDataSet.Tables[0].Rows[0]["MyColumn"].GetNonNull(0);

// -or-

DateTime MyDatetime = MyDataSet.Tables[0].Rows[0]["MyDateColumn"].GetNonNullDate();

Sunday, December 20, 2009

DefaultValue for Properties

When in the Designer for a Form, everyone knows that you can go to the Properties window and change a property on the form or a control. You most likely also know that you can then right-click on any changed property, and choose "Reset" to set it back to its Default Value.

What code do you need in your own controls to get that to work? You need code in two places: in the Constructor to set the value to begin with, and a [DefaultValue] attribute for the Property itself:

public class MyTextBox : TextBox
{
private string m_MyProperty;

public MyTextBox
{
this.m_MyProperty = "";

// As an additional bonus, I'm showing you two ways to do color
this.BackColor = System.Drawing.Color.FromArgb(90, 100, 240);
this.ForeColor = System.Drawing.Color.Firebrick; ;
}

[DefaultValue("")]
public string MyProperty
{
get {return this.m_MyProperty;}
set {this.m_MyProperty = value;}
}
[DefaultValue(typeof(System.Drawing.Color), "90,100,240")]
public override System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
set
{
base.BackColor = value;
}
}
[DefaultValue(typeof(System.Drawing.Color), "Firebrick")]
public override System.Drawing.Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
base.ForeColor = value;
}
}
}

Some properties aren't virtual and can't be overridden. For those properties, you can use "new" instead of "override".

The [DefaultValue] attribute serves two purposes:

  1. It allows the property to be easily Reset to it's Default Value from the Property Sheet.
  2. It prevents the code that sets the default value from actually showing up in the InitializeComponent() method.

That second point is pretty important. Let me illustrate why. Say that you've not used the [DefaultValue] attribute in your TextBox, but simply set the value of the BackColor and ForeColor properties in your MyTextBox constructor, like this:

public class MyTextBox : TextBox
{
public MyTextBox()
{
this.BackColor = Color.DarkGreen;
this.ForeColor = Color.Firebrick;
}
}

When you drop this TextBox onto a Form, the BackColor and ForeColor will be explicitly set in the code  generated in the  InitializeComponent() method of the Form (IOW, you’ll have the code to set BackColor and ForeColor for every TextBox you drop on your design surface). Consequently, if you later decide to change the color in your MyTextBox class, you have to revisit every single Form that you ever dropped a MyTextBox on, to change it to the new default value. Not good!

Conversely, let's look at the opposite scenario where you do include a property with a [DefaultValue] attribute, but you don't initialize that in the constructor. So, your class looks like this:

public class MyTextBox : TextBox
{
// bad code! Do not try this at home! ;0)
public MyTextBox()
{
}

[DefaultValue(typeof(System.Drawing.Color), "DarkGreen")]
public override System.Drawing.Color BackColor
{
get {return base.BackColor;}
set {base.BackColor = value;}
}
[DefaultValue(typeof(System.Drawing.Color), "Firebrick")]
public override System.Drawing.Color ForeColor
{
get {return base.ForeColor;}
set {base.ForeColor = value;}
}
}

When you drop MyTextBox on a Form now, it will default to a color of SystemColors.Control for the BackColor property and SystemColors.WindowText for the ForeColor property, and the IDE will generate the code in the InitializeComponent() method of the Form where you dropped MyTextBox onto, unless you go to the Property Sheet, right-click the BackColor and choose "Reset" (and likewise for ForeColor). Once you do this, the correct color appears for the TextBox in the Designer, the IDE removes the setting of the BackColor and ForeColor properties in the InitializeComponent() method and all looks fine ... until you decide to change the default to something else in your MyTextBox class ... since your TextBox doesn't initialize its BackColor and ForeColor properties in its constructor, the Form goes back to displaying SystemColors.Control and SystemColors.WindowText (even though it's not hard-coded in the InitializeComponent() method).  Again, this is not good!

Suffice it to say that you simply must do both -- initialize it in the constructor and specify the [DefaultValue] attribute. Just something that you're going to have to remember to do!!