July 2009 - Posts
Whether it is a brisk walk, a game of golf, killing aliens, or a good book, everyone needs a little down time to recharge. And several times a year, it is good to extend that downtime and let the mind relax and run free.
I am lucky enough to have a sister with a lake house (and every possible accoutrement) and a brother with a gentleman’s farm. So I get some serious recharging time each year to breath the fresh lake air, enjoy water sports, play lawn games, visit the horses, chickens, and pigs, eat great food fresh from the garden, and just hang out with enjoyable relatives.
Hope you find some time to recharge this summer!
Coalesce Operator
The coalesce operator, also called the null coalescing operator, is new in VB 9 and C# 2.0. It helps you work with nulls (Nothing in VB).
The coalesce operator involves two expressions. The first expression must evaluate to a nullable type or reference type. The first expression is the result if the first expression evaluates to a non-null value. The second expression is the result if the first expression evaluates to null (Nothing in VB).
In C#:
string result = value ?? "<Empty>";
?? is the coalesce operator in C#. It is of the general form:
Exression ?? NullResult
In VB:
Dim result As String = If(value, "<Empty>")
The If keyword is used as the coalesce operator in VB. It is of the general form:
If(Expression ?? NullResult)
To use the coalesce operator, the value variable in the above examples must be a nullable type or a reference type. If value is non-null, value is returned. If value is null (Nothing in VB), the second parameter is returned. Here are some examples:
- value = “CA” result = “CA”
- value = String.Empty result = String.Empty
- value = null (nothing in VB) result = “<Empty>”
Use the coalesce operator any time you need to have special case handling of a null.
Ternary Operator
The ternary operator, also called the conditional operator, is new in VB 9 and has been in C# from the beginning. For a general description of a ternary operator, see this.
The ternary operator involves three expressions (hence the name ternary). The first expression is a Boolean expression that evaluates to true or false. The second expression is the result if the first expression is true. The third expression is the result if the first expression is false.
In C#:
string result = value == null ? "<Empty>" : "<Full>";
? is the ternary operator in C#. It is of the general form:
Condition ? TrueResult : FalseResult
In VB:
Dim result As String = If(value Is Nothing, "<Empty>", "<Full>")
The If keyword is used as the ternary operator in VB. It is of the general form:
If(Condition, TrueResult, FalseResult)
NOTE: In VB, you should always favor the ternary If() syntax over the old IIF syntax, which was inefficient and not recommended for use.
In the examples, if first expression is true, the second expression is returned. If the first expression is false, the third expression is returned. Here are some examples:
- value = “CA” result = “<Full>”
- value = String.Empty result = “<Full>”
- value = null (nothing in VB) result = “<Empty>”
Use the ternary operator any time you need two different results depending on a Boolean expression.
Enjoy!
If you are using Visual Studio 2008 (.NET Framework 3.5) or newer, you can leverage inferred typing. Inferred typing allows the compiler to infer your data types instead of you having to explicitly define the type. Variables that use inferred typing are still strongly typed, they just depend on .NET for determining (or inferring) the type.
Inferred typing is sometimes referred to as “duck typing”. This is from the truth that if something looks like a duck and quacks like a duck, it must be a duck. For more general information on inferred typing (or type inference), see this.
The compiler infers your data type based on its context. Here are some examples.
In C#:
var i = 1;
var x = "Hello";
var arr = new int[] {1, 2, 3, 4};
var c = new Customer();
var y = null; // Generates an error
To use inferred typing in C#, use the var keyword as in the above examples. Note that var is not short for variant and does not imply that the variables are weakly typed. Rather, the var keyword defines that the compiler should infer the type based on the context.
In VB:
Dim i = 1
Dim x = "Hello"
Dim arr() = New Integer() {1, 2, 3, 4}
Dim c = new Customer()
Dim y = Nothing
To use inferred typing in VB, you need to set Option Infer On:
- Double-Click on My Project under Solution Explorer.
- Click the Compile tab.
- Set Option Infer On.
If Option Infer is On, simply Dim the variable to take advantage of inferred typing. This does not imply that the variables are weakly typed or are of type Object. Rather, using Dim without a data type when Option Infer is On defines that the compiler should infer the type based on the context.
In the first example under either language, the inferred type of i is integer because it is assigned to 1. In the second example, x is inferred to be of type string. The arr variable is inferred to be of an integer array type.
The fourth example infers the type of c to be Customer and creates a new instance of the Customer class. This is less necessary as a shortcut in VB where you can also do this:
Dim c as New Customer
In the last example, VB infers the type of y to be Object. C# won’t attempt to infer the type and instead generates a compile error.
You can view the type that is inferred by hovering over the value in the Visual Studio text editor. In VB, hover over the variable. In C#, hover over the var keyword.
BUT – is this a good idea? Should you be leaving it up to the compiler to define your types?
The recommended best practice regarding inferred typing is as follows:
- Do NOT use inferred typing for variables of intrinsic types (like in the above integer and string examples).
- DO use inferred typing when creating a new instance, such as in the new Customer example.
- DO use inferred typing when dealing with anonymous types (see this or this).
- DO use inferred typing when working with LINQ and Lambda expressions.
Though a quick Bing of this topic shows that not all developers agree with these guidelines.
A common use of inferred typing is with LINQ and Lambda expressions because the data types of these expressions are often long and tedious.
For example:
In C#:
List<int> myList = new List<int>() { 1, 6, 8, 3, 2, 4, 15 };
var result = from item in myList
where item < 5
select item;
In VB:
Dim myArr() As Integer = {1, 6, 8, 3, 2, 4, 15}
Dim myList As List(Of Integer) = myArr.ToList
Dim result = From item In myList _
Where item < 5 _
Select item
NOTE: VB does not support List initializers but it does support array initializers. So to simplify the amount of code, an array initializer was used and then the array was converted to a List.
In both cases, the result variable type is System.Collections.Generic.IEnumerable<int>. Yuck!
Enjoy!
There are situations where you may want to know who called a particular method in your application. Maybe you want to log it or use the information for debugging purposes. Whatever the reason, you can get information on the call stack using the StackTrace class.
A stack trace is basically information on the active stack while a program is running. See this for more basic information on stack trace.
The .NET StackTrace class is detailed here. This post provides some examples of using the StackTrace class to obtain information about the call stack.
If you want to view the entire StackTrace, you can loop through each stack frame.
NOTE: Be sure to set a reference to System.Diagnostics.
In C#:
private string MyMethod(string s1)
{
StackTrace st = new StackTrace();
foreach (StackFrame sf in st.GetFrames())
{
Debug.WriteLine("Calling class name: " +
sf.GetMethod().ReflectedType.Name);
Debug.WriteLine("Calling method name: " + sf.GetMethod().Name);
Debug.WriteLine("Full method signature: " +
sf.GetMethod().ToString());
}
return String.Empty;
}
In VB:
Public Function MyMethod(ByVal s1 As String) As String
Dim st As New StackTrace
For Each sf As StackFrame In st.GetFrames
Debug.WriteLine("Calling class name: " & _
sf.GetMethod.ReflectedType.Name)
Debug.WriteLine("Calling method name: " & sf.GetMethod.Name)
Debug.WriteLine("Full method signature: " & sf.GetMethod.ToString)
Next
Return String.Empty
End Function
The GetMethod function gets the method executing in the defined stack frame. The 0th stack frame is the currently executing method. The 1st stack frame is the method that called it, the 2nd stack frame is the method that called that method, and so on up the call stack.
The Name property defines the name of the method. The ReflectedType property defines the class of the calling method and the ReflectedType.Name property provides the name of that class.
The ToString method provides the full method signature. Regardless of the language you are using, the signature will always be displayed in C# style syntax.
For example, the first few stack frames from my example application are as follows:
Calling class name: TestWin
Calling method name: MyMethod
Full method signature: System.String MyMethod(System.String)
Calling class name: TestWin
Calling method name: WorkWithMethods
Full method signature: Void WorkWithMethods()
Calling class name: TestWin
Calling method name: TestWin_Load
Full method signature: Void TestWin_Load(System.Object, System.EventArgs)
If you only want information about the direct caller, you can access the first stack frame.
In C#:
StackTrace st = new StackTrace();
StackFrame sfFirst = st.GetFrame(1);
Debug.WriteLine("Calling class name: " +
sfFirst.GetMethod().ReflectedType.Name);
Debug.WriteLine("Calling method name: " + sfFirst.GetMethod().Name);
In VB:
Dim st As New StackTrace
Dim sfFirst As StackFrame = st.GetFrame(1)
Debug.WriteLine("Calling class name: " & _
sfFirst.GetMethod.ReflectedType.Name)
Debug.WriteLine("Calling method name: " & sfFirst.GetMethod.Name)
This code provides the name of the class and method that directly called the current method:
Calling class name: TestWin
Calling method name: WorkWithMethods
Enjoy!
There are often times that you need to find specific controls on a Windows Form (WinForm). For example, you may want to find all of the TextBoxes to clear their contents, set their background color, or hook up events. You may want to find all of the checkboxes to check or uncheck them. Or find all of the buttons to hook up events or change their color.
Since a Windows Form has a Controls collection, it may seem obvious to simply loop through the Control's collection to find all of the desired controls.
But many of the controls provided in WinForms can contain other controls. Take this form for example:
This form contains two Panel controls. The top Panel control contains several Label controls and a ComboBox. The bottom Panel control contains Labels and TextBoxes.
If you look at the Controls collection for the form in this example, you find that it contains only two controls. Only the two Panel controls are actually on the form. The other controls are on the Panel controls. So to find those controls, you need to loop through the Controls collection of each Panel.
If you put a Tab control on a Panel control, then the TextBox controls would be on the Tab control which is on the Panel control. The easiest way to look for controls that could be on other controls which in turn could be on other controls, is to use recursion.
Recursion is basically calling a method from the method itself. (You can find a more detailed explanation here.) The following demonstrates recursion to find all of the TextBoxes on a form.
In C#:
private void ProcessControls(Control ctrlContainer)
{
foreach (Control ctrl in ctrlContainer.Controls)
{
if (ctrl.GetType() == typeof(TextBox))
{
// Do whatever to the TextBox
}
if (ctrl.HasChildren)
ProcessControls(ctrl);
}
}
In VB:
Private Sub ProcessControls(ByVal ctrlContainer As Control)
For Each ctrl As Control In ctrlContainer.Controls
If TypeOf ctrl Is TextBox Then
' Do whatever to the TextBox
End If
' If the control has children,
' recursively call this function
If ctrl.HasChildren Then
ProcessControls(ctrl)
End If
Next
End Sub
The code begins by processing each control within the defined container control using the container’s Controls collection. The control’s type is checked to determine if it is the desired type of control. This technique can be used to look for any type of control or multiple types of controls.
The HasChildren property of the control is checked to determine if the control itself is a container for other controls. If so, it calls this method recursively using the control as the container control.
This method resides in the Form and is called as follows.
In C#:
ProcessControls(this);
In VB:
ProcessControls(Me)
The form itself is passed into the ProcessControls method as the highest level container. If you only want to search for controls within some other container, pass it instead of the entire form. For example, if you only wanted to search for TextBox controls on Panel2 or on controls that are on Panel2, you would pass Panel2 instead of the form (this or me).
Finding Controls By Name
If you want to find a control or set of controls by name, there is an easier way.
In C#:
Control[] ctrls = this.Controls.Find("TextBox1", true);
In VB:
Dim ctrls() As Control = Me.Controls.Find("TextBox1", True)
This code uses the Find method of the controls collection. The first parameter defines the name of the control(s) to find. If the second parameter is true, the Find will search through all child controls, performing the recursive operation for you. If it is false, it will only look through the form’s Controls collection.
When I was first working with the Find method, I thought it odd that it returned an array since you can only put one control on the form named “TextBox1”. However, you can add multiple controls with the same name using Controls.Add. Also, if you build a composite user control you can also put a TextBox1 on the user control. So if you also have a TextBox1 on the form, the Find method will find two controls named “TextBox1”.
Enjoy!

I remember watching Doctor Who in college when Tom Baker was the doctor. Those were the days before VCR recording and TiVo, so we had to rush home to ensure we caught that next episode.
Tom Baker’s Doctor Who was smart, logical, and a little crazy. And always with the striped double-length scarf.

I was thrilled when I found out that the BBC was starting Doctor Who again and we added it to our TiVo choices. I got my oldest daughter so hooked on it that she found most of the old episodes on the internet (even some from the 1960’s) and watched them.
So it was not surprising that we took the opportunity to include Doctor Who in our trip to London in January, 2009.
Our first Doctor Who stop was a special exhibit at Earl’s Court in London. My daughter even got a brief ride in the TARDIS….
… and had to fight off the Dalek. Exterminate!!!
Second stop was the Doctor Who Exhibition in Cardiff.

Final stop was seeing the current Doctor Who (David Tennant) in Hamlet. (No cameras allowed during the play, so we just took a picture with the play bill during intermission.)
Are you a Doctor Who fan? Share your thoughts on Doctor Who.
Last night, my husband (who is also a .NET developer) asked me about sorting a DataTable by user-defined columns. (Yes, we are a pretty exciting couple!)
Most developers that work with DataTables know how to sort the DataTable using a DataView.Sort. If you are interested in that technique, you can view the msdn documentation here. This post is about sorting using Linq.
NOTE: Be sure to set a reference to System.Data.DatasetExtensions.
If you know ahead of time which columns of the DataTable should be sorted, you can use LINQ to DataTables like this.
In C#:
var query = from c in dt.AsEnumerable()
orderby c.Field<DateTime?>("LastPurchaseDate"),
c.Field<string>("LastName") descending
select c;
DataView dv = query.AsDataView();
In VB:
Dim query = From c In dt.AsEnumerable _
Order By c.Field(Of DateTime?)("LastPurchaseDate"), _
c.Field(Of String)("LastName") Descending
Dim dv As DataView = query.AsDataView
The dt variable represents the table you wish to sort. The AsEnumerable is an extension method on the DataTable that allows you to use Linq with it. The Order By clause takes any number of columns. You must specify the data type and then the column name or index. To sort descending, use the Descending keyword.
A nullable DateTime (represented by DateTime?) ensures that the code correctly handles any null values in the table.
In the above examples, the data is sorted first by LastPurchaseDate (ascending) and then by LastName (descending).
The resulting DataView can be bound to a grid or other control.
If you prefer Lambda expressions, you can do this same sort as follows:
In C#:
var query2 = dt.AsEnumerable()
.OrderBy(c=> c.Field<DateTime?>("LastPurchaseDate"))
.ThenByDescending(c=> c.Field<string>("LastName"));
DataView dv2 = query.AsDataView();
In VB:
Dim query2 = dt.AsEnumerable _
.OrderBy(Function(c) c.Field(Of DateTime?)("LastPurchaseDate")) _
.ThenByDescending(Function(c) c.Field(Of String)("LastName"))
Dim dv2 As DataView = query.AsDataView
This code performs the same sort as the prior examples.
But neither of these techniques work well if you want the user to select any number of columns to use for the sort. In that case, you need something a little more full-featured.
The first step to building a more generalized sort is to build your own comparer class.
In C#:
private class RowComparer : IComparer<DataRow>
{
public Dictionary<int, SortOrder> SortColumns { get; set; }
public int Compare(System.Data.DataRow x, System.Data.DataRow y)
{
int returnValue = 0;
foreach (int key in SortColumns.Keys)
{
int compareResult ;
// Check for nulls
if (x.ItemArray[key] == DBNull.Value
&& y.ItemArray[key] == DBNull.Value)
compareResult = 0;
else if (x.ItemArray[key] == DBNull.Value)
compareResult = -1;
else if (y.ItemArray[key] == DBNull.Value)
compareResult = 1;
else
{
// Execute the compare based on the column type
if (x.Table.Columns[key].DataType.Name ==
typeof(Decimal).Name)
compareResult =
Decimal.Compare((decimal)x.ItemArray[key],
(decimal)y.ItemArray[key]);
else if (x.Table.Columns[key].DataType.Name ==
typeof(DateTime).Name)
compareResult =
DateTime.Compare((DateTime)x.ItemArray[key],
(DateTime)y.ItemArray[key]);
else
// Compare anything else as a string
compareResult =
String.Compare(x.ItemArray[key].ToString(),
y.ItemArray[key].ToString());
}
if (compareResult != 0)
{
returnValue =
SortColumns[key] == SortOrder.Ascending ?
compareResult: -compareResult;
break;
}
}
return returnValue;
}
}
In VB:
Public Class RowComparer
Implements IComparer(Of DataRow)
Private _sortColumns As Dictionary(Of Integer, SortOrder)
Public Property SortColumns() As Dictionary(Of Integer, SortOrder)
Get
Return _sortColumns
End Get
Set(ByVal value As Dictionary(Of Integer, SortOrder))
_sortColumns = value
End Set
End Property
Public Function Compare(ByVal x As System.Data.DataRow, _
ByVal y As System.Data.DataRow) As Integer _
Implements System.Collections.Generic.IComparer( _
Of System.Data.DataRow).Compare
Dim returnValue As Integer = 0
For Each key As Integer In SortColumns.Keys
Dim compareResult As Integer
' Handle DBNull.
Dim xValue As Object = _
If(x.Item(key) Is DBNull.Value, Nothing, x.Item(key))
Dim yValue As Object = _
If(y.Item(key) Is DBNull.Value, Nothing, y.Item(key))
' Execute the appropriate compare based on the column type
Select Case x.Table.Columns(key).DataType.Name
Case GetType(Decimal).Name
compareResult = _
Decimal.Compare(CType(xValue, Decimal), _
CType(yValue, Decimal))
Case GetType(DateTime).Name
compareResult = _
DateTime.Compare(CType(xValue, DateTime), _
CType(yValue, DateTime))
Case Else
' Compare anything else as a string
compareResult = _
String.Compare(x.Item(key).ToString, _
y.Item(key).ToString)
End Select
If compareResult <> 0 Then
returnValue = _
If(SortColumns(key) = SortOrder.Ascending, _
compareResult, -compareResult)
Exit For
End If
Next
Return returnValue
End Function
End Class
This class defines a comparer to use when sorting a DataRow. The sortColumns property is a Dictionary that stores the index of the column to use as the sort, and a SortOrder to define whether to sort ascending or descending. Presumably, the values for this Dictionary were obtained from the user.
The Compare method does all of the work. It compares any two DataRows to determine how each row should be sorted against any other row. The method processes each of the sortColumns.
First, it checks for a DBNull. The DBNull checking is different in the C# code and VB code. The C# code checks for a DBNull and manually sets the result of the compare. The VB code simply sets the value to Nothing if it is DBNull.
The code performs a compare based on the type of column. This is necessary because the user would expect decimals to sort as numbers, not as strings. You can add any other data types here as you require. Any data types not specifically handled will be handled as a string.
Note that a Select/Case statement was used in VB, but if/else if was used in C#. This is because C# requires its switch/case statements to switch based on constant values, not variables.
Regardless of the datatype, the result of the type-specific compare method is:
- -1: If the value of x is less than the value of y.
- 1: if the value of x is great than the value of y.
- 0: if the value of x and y are equal.
If the resulting value is not 0, the loop can exit because the comparison is complete. If the resulting value is 0, meaning the columns are equal, the loop continues and the next column in the set of sort columns is checked.
You then use this class as follows.
In C#:
Dictionary<int, SortOrder> sortColumns =
new Dictionary<int, SortOrder>();
sortColumns.Add(2,SortOrder.Ascending);
sortColumns.Add(1, SortOrder.Descending);
RowComparer comp = new RowComparer();
comp.SortColumns = sortColumns;
var query3 = dt.AsEnumerable().OrderBy(q => q, comp);
DataView dv3 = query3.AsDataView();
In VB:
Dim sortColumns As New Dictionary(Of Integer, SortOrder)
sortColumns.Add(2, SortOrder.Ascending)
sortColumns.Add(1, SortOrder.Descending)
Dim comp As New RowComparer
comp.SortColumns = sortColumns
Dim query3 = dt.AsEnumerable.OrderBy(Function(q) q, comp)
Dim dv3 As DataView = query3.AsDataView
The first set of code sets up the Dictionary of sort columns. The sort columns are hard-coded in this example, but presumably they would come from the user.
The code then creates an instance of the new RowComparer class and passes in the set of columns.
NOTE: You could define a parameterized constructor and pass in the sorted columns instead of using a property, if desired.
The Lambda expression to perform the sort is then greatly simplified. It just passes in the desired comparer.
That’s it. You now have the ability to sort your DataTable using any user-defined set of columns.
Enjoy!
As with most things in Visual Studio, there are many ways to export data from your .NET application to an Excel spreadsheet. This post covers one straightforward technique.
First, set a reference to the desired version of the Microsoft Excel Object Library from the COM tab of the Add Reference dialog. The resulting reference appears as Microsoft.Office.Interop.Excel.
Import the Microsoft.Office.Interop.Excel library in your code file. To keep the namespaces clear, consider using an alias.
In C#:
using Excel = Microsoft.Office.Interop.Excel;
using System.Reflection;
NOTE: C# also requires a reference to System.Reflection to use the Missing.Value field. This is required because C# 3.0 does not support optional parameters. Every parameter must have a value, so Missing.Value is used to provide a value for all unspecified optional parameters.
In VB:
Imports Excel = Microsoft.Office.Interop.Excel
The rest of the code is presented in VB and C# and then discussed below. It is provided in one big chunk to make it easier to copy/paste into your application.
In C#:
Excel.Application oXL;
Excel.Workbook oWB;
Excel.Worksheet oSheet;
Excel.Range oRange;
// Start Excel and get Application object.
oXL = new Excel.Application();
// Set some properties
oXL.Visible = true;
oXL.DisplayAlerts = false;
// Get a new workbook.
oWB = oXL.Workbooks.Add(Missing.Value);
// Get the active sheet
oSheet = (Excel.Worksheet)oWB.ActiveSheet ;
oSheet.Name = "Customers";
// Process the DataTable
// BE SURE TO CHANGE THIS LINE TO USE *YOUR* DATATABLE
DataTable dt = Customers.RetrieveAsDataTable();
int rowCount = 1;
foreach (DataRow dr in dt.Rows)
{
rowCount += 1;
for (int i = 1; i < dt.Columns.Count+1; i++)
{
// Add the header the first time through
if (rowCount==2)
{
oSheet.Cells[1, i] = dt.Columns[i - 1].ColumnName;
}
oSheet.Cells[rowCount, i] = dr[i - 1].ToString();
}
}
// Resize the columns
oRange = oSheet.get_Range(oSheet.Cells[1, 1],
oSheet.Cells[rowCount, dt.Columns.Count]);
oRange.EntireColumn.AutoFit();
// Save the sheet and close
oSheet = null;
oRange = null;
oWB.SaveAs("test.xls", Excel.XlFileFormat.xlWorkbookNormal,
Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Excel.XlSaveAsAccessMode.xlExclusive,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value);
oWB.Close(Missing.Value, Missing.Value, Missing.Value);
oWB = null;
oXL.Quit();
// Clean up
// NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
In VB:
Dim oXL As Excel.Application
Dim oWB As Excel.Workbook
Dim oSheet As Excel.Worksheet
Dim oRange As Excel.Range
' Start Excel and get Application object.
oXL = New Excel.Application
' Set some properties
oXL.Visible = True
oXL.DisplayAlerts = False
' Get a new workbook.
oWB = oXL.Workbooks.Add
' Get the active sheet
oSheet = DirectCast(oWB.ActiveSheet, Excel.Worksheet)
oSheet.Name = "Customers"
' Process the DataTable
'BE SURE TO CHANGE THIS LINE TO USE *YOUR* DATATABLE
Dim dt As Data.DataTable = Customers.RetrieveAsDataTable
' Create the data rows
Dim rowCount As Integer = 1
For Each dr As DataRow In dt.Rows
rowCount += 1
For i As Integer = 1 To dt.Columns.Count
' Add the header the first time through
If rowCount = 2 Then
oSheet.Cells(1, i) = dt.Columns(i - 1).ColumnName
End If
oSheet.Cells(rowCount, i) = dr.Item(i - 1).ToString
Next
Next
' Resize the columns
oRange = oSheet.Range(oSheet.Cells(1, 1), _
oSheet.Cells(rowCount, dt.Columns.Count))
oRange.EntireColumn.AutoFit()
' Save the sheet and close
oSheet = Nothing
oRange = Nothing
oWB.SaveAs("test.xls")
oWB.Close()
oWB = Nothing
oXL.Quit()
' Clean up
' NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
NOTE: The VB code code above is set up to run with Option Strict ON.
The code begins by declaring the variables used in the code. Notice that in both C# and in VB this makes use of the namespace alias as shown at the very beginning of this post.
The code then starts Excel and makes it visible. If you want to create the spreadsheet without making Excel visible to the user, you could set the Visible to false instead.
A workbook is added, the active sheet of the workbook is referenced, and the sheet is given a name.
The DataTable is then processed. You can use any DataTable here. The code loops through the rows of the DataTable and for each row it loops through the columns. The first time through the columns it adds column headings.
To make the spreadsheet easier to read, the columns are then resized to show the contents of the cells. If you have lots of data in a cell, you may want to skip this step.
The workbook is then saved. Since no directory was specified, Excel will save the file to your My Documents folder. Notice the massive number of parameters in the C# code when calling the SaveAs (and Close) methods. This is required because C# does not have optional parameters. So the code must fill in each and every parameter in the call. VB does support optional parameters, so does not need to set the extra parameters.
NOTE: C# is getting optional parameters in .NET 4.0 (VS 2010).
Finally, the code is cleaned up. Since Excel is accessed through COM interop, the double garbage collection code is added to ensure Excel is correctly closed. See this link for more information on why this specific garbage collection code is recommended.
Enjoy!
There are many things in VB.NET and C# that are very similar. So if you know that basic syntax of both languages, it is relatively easy to convert a few lines of code from one to the other.
Not so much with events. This is one area where it is easy to get tripped up when you have to convert code from one language to the other.
To demonstrate how to define events in VB vs C#, this example is a WinForms user control that raises a single custom event. It is a very simple user control that inherits from a TextBox. The point is not to demonstrate the proper way to create a user control, but rather just to focus on adding an event to the user control.
The event is a ValueChanged event. This event is raised when the associated TextBox TextChanged event occurs. While this might be less than useful, it provides a simple example of defining event in VB and C#.
One other note before diving into the code. The event in this post follows the suggested pattern of defining event parameters of type object and eventArgs (or a subclass thereof). If you are coming from earlier versions of VB or C, you may be used to creating events with other types of parameters.
For example, you could do this in VB:
Public Class SampleUserControl
' Declare an event
Public Event ValueChanged(value as Integer)
Private Sub SampleUserControl_TextChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.TextChanged
' Raise the event
RaiseEvent ValueChanged(42)
End Sub
End Class
While this makes the coding extremely easy (what you see above is all you need), it does not follow the pattern or best practices as defined by the “Framework Design Guidelines”.
So lets get to the real example. Since the code is so different, instead of showing a little at a time in both languages, this shows the VB code first. Then it goes through the same features using C#.
In VB:
Public Class SampleUserControl
' Declare an event
Public Event ValueChanged(ByVal sender As Object, _
ByVal e As ValueChangedEventArgs)
Private Sub SampleUserControl_TextChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.TextChanged
Dim tb As TextBox = DirectCast(sender, TextBox)
Dim value As Integer = 0
If IsNumeric(tb.Text) Then
value = CType(tb.Text, Integer)
End If
' Raise the event
RaiseEvent ValueChanged(sender, _
New ValueChangedEventArgs(value))
End Sub
End Class
This class is a user control that inherits from a TextBox. In VB, by convention the inherits statement is in the associated designer file (SampleUserControl.Designer.vb):
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class SampleUserControl
Inherits TextBox
…
End Class
The code in the class first declares the event. Notice that it uses the standard sender and e parameters. It then raises the event when the associated TextBox text is changed. If the TextBox Text property value is numeric, it passes the value as part of the event arguments; otherwise, it passes 0.
Since the ValueChanged event required additional data associated with it, it did not use the standard EventArgs class. Rather, it defined its own ValueChangedEventArgs class:
Public Class ValueChangedEventArgs
Inherits EventArgs
Private _NewValue As Integer
Public Property NewValue() As Integer
Get
Return _NewValue
End Get
Set(ByVal value As Integer)
_NewValue = value
End Set
End Property
Public Sub New(ByVal currentNewValue As Integer)
NewValue = currentNewValue
End Sub
End Class
This class inherits from EventArgs. Any desired properties can be added to this class. In this case, only one property was added for the new value. The constructor requires that this new value be passed in as a parameter.
So using a specialized EventArgs class required a little more code than the first VB example. But this is still basically straightforward.
To summarize the steps:
- Declare the event in the User Control.
- Raise the event in the User Control where appropriate.
- Define the class that inherits from EventArgs.
So now let’s move on to C#:
// Declare a delegate
public delegate void ValueChangedEventHandler(object sender,
ValueChangedEventArgs e);
public partial class SampleUserControl : TextBox
{
public SampleUserControl()
{
InitializeComponent();
}
// Declare an event
public event ValueChangedEventHandler ValueChanged;
protected virtual void OnValueChanged(ValueChangedEventArgs e)
{
if (ValueChanged != null)
ValueChanged(this,e);
}
private void SampleUserControl_TextChanged(object sender,
EventArgs e)
{
TextBox tb = (TextBox)sender;
int value;
if (!int.TryParse(tb.Text, out value))
value = 0;
// Raise the event
OnValueChanged( new ValueChangedEventArgs(value));
}
}
This code declares a delegate outside of the class. The class is a user control that inherits from a TextBox.
The first set of code is the constructor for the user control. The code then declares the event using the delegate.
Since there is no RaiseEvent function in C#, you have to create a method that generates your event. This is the OnValueChanged method shown above. The convention is to use the word “On” and the name of your event. The TextBox TextChanged event then calls OnValueChanged to raise the event.
Since the ValueChanged event required additional data associated with it, it did not use the standard EventArgs class. Rather, it defined its own ValueChangedEventArgs class:
public class ValueChangedEventArgs : EventArgs
{
public int NewValue { get; set; }
public ValueChangedEventArgs(int newValue)
{
NewValue = newValue;
}
}
This code is very similar to the VB code. This class inherits from EventArgs. Any desired properties can be added to this class. In this case, only one property was added for the new value. The constructor requires that this new value be passed in as a parameter.
To summarize the steps:
- Declare a delegate outside of the user control class.
- Declare the event in the User Control of the delegate type.
- Create a method to generate the event.
- Call the method to raise the event in the User Control where appropriate.
- Define the class that inherits from EventArgs.
When a form uses your user control, it can hook up the event as needed.
In VB:
Private Sub SampleUserControl1_ValueChanged(ByVal sender As Object, _
ByVal e As ValueChangedEventArgs) _
Handles SampleUserControl1.ValueChanged
Debug.WriteLine("Made it to the event test: " & e.NewValue)
End Sub
This code works because in the designer, the user control is automatically declared WithEvents:
Friend WithEvents SampleUserControl1 As _
SampleApplication.SampleUserControl
Alternatively, you can use the Addhandler statement to hook up the ValueChanged event.
In C#:
private void sampleUserControl1_ValueChanged(object sender,
ValueChangedEventArgs e)
{
Debug.WriteLine("Made it to the event test: " + e.NewValue);
}
For this code to work, you need to use the designer to hook up the events, (which generates the following line of code in the designer file) or add this line of code in the form:
sampleUserControl1.ValueChanged += sampleUserControl1_ValueChanged;
So C# and VB can provide the exact same functionality, but with different steps to get there.
Enjoy!
I learned to program using structured programming techniques with RSTS/E BASIC on a PDP 11/45 and progressed to VAX BASIC on a VAX 11/70. This was before database products were common, so we created our own data structures using hash tables and linked lists. Then came Ingres, and we moved to a relational database system.
After a brief stint on the mainframe, I moved to Silicon Valley and started using C and then C++. This was so long ago, that there was no Microsoft C++ so we used Zortek C++. This was my first introduction to object-oriented programming and I really liked it.
In 1992, I left the corporate world and moved into consulting. One of my first consulting projects was with Alan Cooper, the “father of Visual Basic”. At that point I was introduced to VB 2. It was so similar to what I had done on the VAX, that everything seemed familiar. The only thing I could not figure out how to do was to instance the user-defined types (UDTs). Come to find out, you could not. No real object-oriented programming features were available until VB 4.0.
When .NET first came out, I moved to VB.NET. There was so much that was different, it was nice to at least have some syntax that was familiar.
After spending months and months with .NET, I finally got to the point where I could actually code without looking something up every two minutes.
At that point I thought it prudent to learn C#. That allowed me to more easily attend talks or view sample code that was in C#. With my C++ background, some syntax was still familiar, but much was not (like event handling!).
Over the past few years, I have been doing more C# code, almost exclusively these past few months. I have found some things that I much prefer doing in C#, while others I much prefer VB.
But my clients still ask the question, “Should we move our team to VB.NET or C#?” Here is my reply:
- If you are coming from C or C++ or Java, it is much more natural to move to C#.
- If you are coming from VB 6 (or prior) or from VBA, it is much more natural to move to VB.NET.
- If you are doing lots of “cutting edge” coding, you may want to consider using C# because often examples for cutting-edge topics come out first in C#. (Though Microsoft is working hard to change this. Look at LINQ as an example, the VB and C# 101 examples came out about the same time.)
- If you are doing lots of XML coding, you may want to consider using VB because it has amazing XML features that C# does not have.
I also recommend that everyone get at least a reading knowledge of both languages. Then if an example is in one or the other, or if you go to a presentation in one or the other, you get the basic idea of what is going on.
When displaying text to the user as a title or message, you may want to ensure that the text is presented in proper case (also called title case).
If you are not familiar with the term “title case”, it basically means upper casing the first letter of each word as you would do in a proper title. For example: “Last Name” or “Customer Status Code”.
NOTE: Be sure to define a reference to System.Globalization.
In C#:
string sampleString = "this is a title";
CultureInfo currentCulture =
System.Threading.Thread.CurrentThread.CurrentCulture;
TextInfo currentInfo = currentCulture.TextInfo;
sampleString = currentInfo.ToTitleCase(sampleString);
In VB:
Dim sampleString As String = "this is a title"
Dim currentCulture As CultureInfo = _
System.Threading.Thread.CurrentThread.CurrentCulture
Dim currentInfo As TextInfo = currentCulture.TextInfo
sampleString = currentInfo.ToTitleCase(sampleString)
This code uses the CultureInfo to ensure that the casing is set appropriately based on the current culture. It then uses the ToTitleCase method of the TextInfo class to convert to the appropriate casing.
In VB there is another option to accomplish this task:
Dim sampleString As String = "this is a title"
sampleString = StrConv(sampleString, vbProperCase)
This code uses the StrConv function that is part of the Microsoft.VisualBasic namespace. This function could also be used from C# if you define a reference to the Microsoft.VisualBasic namespace.
The result is:
This Is A Title
Notice that this does not follow the proper case rules that define articles and conjunctions as lower case. Rather, it converts the first letter of every word to upper case and the remainder of the text to lower case.
Enjoy!
This post describes how to populate a WinForms TreeView control from an XML file assuming you are targeting the .NET Framework Version 3.5.
The XML file used in this example looks like this:
<States>
<State name="California">
<Regions>
<Region name="San Luis Obispo">
<Area name="Santa Maria" />
<Area name="Seaside" />
</Region>
<Region name="Silicon Valley">
<Area name="San Jose"/>
<Area name="Sunnyvale"/>
</Region>
</Regions>
</State>
<State name="Wisconsin">
<Regions>
<Region name="Milwaukee">
<Area name ="Mukwanago"/>
<Area name="Germantown"/>
</Region>
<Region name="Fox Valley">
<Area name="Oshkosh" />
<Area name="Appleton" />
</Region>
</Regions>
</State>
</States>
The TreeView will display the following:
State
--- Region
------ Area
The code is provided here in VB and C# and then described in detail below.
NOTE: Be sure to set a reference to System.Core and System.Xml.Linq
In C#:
XElement doc = XElement.Load("testXML.xml");
TreeNode stateNode;
TreeNode regionNode;
foreach (XElement state in doc.Descendants("State"))
{
stateNode = treeView1.Nodes.Add(state.Attribute("name").Value);
foreach (XElement region in state.Descendants("Region"))
{
regionNode =
stateNode.Nodes.Add(region.Attribute("name").Value);
foreach (XElement area in region.Descendants("Area"))
{
regionNode.Nodes.Add(area.Attribute("name").Value);
}
}
}
In VB:
Dim doc As XElement = XElement.Load("testXML.xml")
Dim stateNode As TreeNode
Dim regionNode As TreeNode
For Each state As XElement In doc...<State>
stateNode = TreeView1.Nodes.Add(state.@name)
For Each region As XElement In state...<Region>
regionNode = stateNode.Nodes.Add(region.@name)
For Each area As XElement In region...<Area>
regionNode.Nodes.Add(area.@name)
Next
Next
Next
In both cases, the XML is first retrieved from a file. An in-memory XML string could be used instead.
Three for/each loops are used, one for each level of the TreeView hierarchy.
The states are processed first. A node is added for each state name.
The regions are processed next. A node is added under the state for each region name.
Finally, the areas are processed. A node is added under the region for each area name.
Notice that the VB code leverages XML literals. The C# code uses XElements (which also work in VB).
Enjoy!
There are often times that you need to write out text files containing data managed by your application. For example, you may need to write out a file to be read by another system. In many cases, this file needs to have a particular format, with justified or aligned columns like this:
000001 Baggins Bilbo 20090711
000002 Baggins Frodo 20090701
000003 Gamgee Samwise 20090720
000004 Cotton Rosie 20090721
The first column is the Id, padded with 0’s. The remaining columns are the last name, first name, and date of last edit in YYYYMMDD format.
NOTE: If you can format your text file as XML instead, see this.
The StringBuilder class provides a method that makes this formatting a snap.
In C#:
using System.IO;
using System.Text;
StringBuilder sb = new StringBuilder();
foreach (Customer c in Customers.Retrieve())
{
sb.AppendFormat("{0,-8}{1,-20}{2,-10}{3,-10:yyyyMMdd}{4}",
c.CustomerId.ToString().PadLeft(6, '0'),
c.LastName,
c.FirstName,
c.LastEditDate,
Environment.NewLine);
}
File.WriteAllText("Test.txt", sb.ToString());
In VB:
Dim sb As New System.Text.StringBuilder
For Each c As Customer In custList
sb.AppendFormat("{0,-8}{1,-20}{2,-10}{3,-10:yyyyMMdd}{4}", _
c.CustomerId.ToString.PadLeft(6, "0"c), _
c.LastName, _
c.FirstName, _
c.LastEditDate, _
Environment.NewLine)
Next
My.Computer.FileSystem.WriteAllText("Test.txt", sb.ToString, False)
The code starts by creating an instance of the StringBuilder class. It then loops through each customer in the list of customers. See this for the code that creates the customer list. NOTE: A LastEditDate was added to the Customer class (not shown) in order to demonstrate date formatting.
You do not have to have business objects to use this code. You could instead loop through a DataSet, DataTable, or DataReader to get the data for your file.
The key to formatting the data into columns is the AppendFormat method of the StringBuilder. This method uses the composite formatting features of the .NET framework to build the string. In this case, the formatting parameter is a set of indexed placeholders, called format items, that look like this:
{index, length:formatString}
The index matches the formatting string with the parameter that follows. For example, index 0 is the first parameter after the format string, which is the CustomerId. Index 1 is the second parameter after the format string, which is the LastName. And so on.
The optional length defines the amount of space that is provided for the column. The system will automatically pad the field with spaces to the defined width. A positive length value will align the text to the right. A negative value aligns the text to the left.
The optional formatString defines a standard or custom format string using the .NET formatting syntax.
The format items used in this example are as follows:
- {0, –8}: Defines that the first parameter will fill 8 characters and will be left justified. This is the CustomerId. Notice that the PadLeft method is used by the CustomerId property to pad the Id field with zeros.
- {1, –20}: Defines that the second parameter will fill 20 characters and will be left justified. This is the last name.
- {2,-10}: Defines that the third parameter will fill 10 characters and will be left justified. This is the first name.
- {3,-10:yyyyMMdd}: Defines that the fourth parameter will fill 10 characters and will be left justified. The value will be formatted as a date in YYYYMMDD format. This is the last edit date.
- {4}: Defines that the fifth parameter will be used “as is”. This is the new line constant, which is inserted to ensure each customer is on a separate line.
The final part of the code uses the ToString method of the StringBuilder to convert the StringBuilder text to a string. It then writes that string to the defined file.
To read this file back into your application as an in-memory DataTable, see this link. To read the file back in using VB's TextFieldParser class, see this link.
Enjoy!
If you are building applications in .NET to manage data for a business, you are most likely creating business object classes. Depending on the business, these classes could include Customer, Product, Order, Invoice, PurchaseOrder, Employee, TimeCard and so on.
A simple sample Customer class is shown here.
There are some features that all business objects need to support. For example:
- Entity State: The business object needs to track whether it is new, updated, or deleted so it can make the appropriate change to the database.
- Validation: Is the current data defined for the business object valid?
- Save: Saves the changes to the database.
Notice that data retrieval functionality is not included in this list. That is because in most cases, you may never retrieve a single business object. Rather, you would retrieve a set of them. For example, all active orders or all customers with overdue invoices. So the retrieve functionality is not included in the class that manages a single object. (Most on this in a later post.)
Instead of repeating this common functionality in each business object, it makes sense to build a base class. Each business object can then inherit from this base class to share this common functionality.
So let’s get started.
In C#:
using System;
using System.ComponentModel;
public abstract class BoBase :
IDataErrorInfo,
INotifyPropertyChanged
{
}
In VB:
Imports System.ComponentModel
Public MustInherit Class BOBase
Implements IDataErrorInfo
Implements INotifyPropertyChanged
End Class
The class is abstract (MustInherit in VB) to indicate that it is meant to be a base class and not to be used on its own. An abstract class cannot be instantiated directly, so no other code can create an instance of the class.
The class then implements two interfaces:
- IDataErrorInfo: This interface provides error information that the user interface can use to report validation errors to the user. It works well with the ErrorProvider control provided in WinForms and supports ASP.NET and WPF features.
- INotifyPropertyChanged: This interface ensures that the user interface is notified when a property value changes, keeping your business object and user interface in sync.
We’ll look at the implementation of these interfaces shortly.
First, define the valid set of entity states.
In C#:
protected internal enum EntityStateType
{
Unchanged,
Added,
Deleted,
Modified
}
In VB:
Protected Friend Enum EntityStateType
Unchanged
Added
Deleted
Modified
End Enum
The Enum is declared Protected because the entity state should only be accessible from the business object itself. However, Internal (Friend in VB) was added so that related objects (such as Order and OrderLineItem) could reference the related object state.
The business object state is retained using an EntityState property.
In C#:
protected EntityStateType EntityState { get; private set; }
In VB:
Private _EntityState As EntityStateType
Protected Property EntityState() As EntityStateType
Get
Return _EntityState
End Get
Private Set(ByVal value As EntityStateType)
_EntityState = value
End Set
End Property
This property is protected, but the setter is private. So the business objects that inherit from this class can read the entity state, but only the base class can set the value.
Additional properties provide a way to get the entity’s state in an easier fashion. These properties are not required, but they make the base class a little easier to use.
In C#:
[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsDirty
{
get { return this.EntityState != EntityStateType.Unchanged; }
}
[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsNew
{
get { return this.EntityState == EntityStateType.Added; }
}
In VB:
<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsDirty() As Boolean
Get
Return Me.EntityState <> EntityStateType.Unchanged
End Get
End Property
<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsNew() As Boolean
Get
Return Me.EntityState = EntityStateType.Added
End Get
End Property
The IsDirty and IsNew properties are public, so they can be accessed from anywhere. They are marked with two attributes:
- Bindable: Defines whether the property should be used for binding. In this case the value is false because the user interface should not be able to bind to this property.
- Browsable: Defines whether the property should be displayed in the Properties window. Again, the value is false.
Two other properties handle the validation.
In C#:
[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsValid
{
get { return (ValidationInstance.Count == 0); }
}
protected Validation ValidationInstance { get; set; }
In VB:
<Bindable(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsValid() As Boolean
Get
Return (ValidationInstance.Count = 0)
End Get
End Property Private _ValidationInstance As Validation
Protected Property ValidationInstance() As Validation
Get
Return _ValidationInstance
End Get
Private Set(ByVal value As Validation)
_ValidationInstance = value
End Set
End Property
Again, the public property is marked with the Browsable and Bindable attributes. The ValidationInstance property retains an instance of the Validation class for the business object. The code for the Validation class is here.
The constructor creates an instance of the Validation class.
In C#:
protected BoBase()
{
ValidationInstance = new Validation();
}
In VB:
Protected Sub New()
ValidationInstance = New Validation
End Sub
The following is the implementation of IDataErrorInfo.
In C#:
#region IDataErrorInfo Members
[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string Error
{
get { return ValidationInstance.ToString(); }
}
[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string this[string columnName]
{
get { return ValidationInstance[columnName]; }
}
#endregion
In VB:
#Region " Properties required by the IDataErrorInfo"
<Bindable(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property [Error]() As String _
Implements IDataErrorInfo.Error
Get
Return ValidationInstance.ToString
End Get
End Property
<BrowsableAttribute(False)> _
<Bindable(False)> _
Default Protected ReadOnly Property Item(ByVal columnName _
As String) As String _
Implements IDataErrorInfo.Item
Get
Return ValidationInstance.Item(columnName)
End Get
End Property
#End Region
The Error property uses the overridden ToString method of the validation class to return the full list of validation errors.
The Item property provides access to the validation errors given a property name. This property is implemented as the class indexer in C#.
The following in the implementation of INotifyPropertyChanged.
In C#:
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
In VB:
#Region " Events required by INotifyPropertyChanged"
Public Event PropertyChanged(ByVal sender As Object, _
ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
Implements INotifyPropertyChanged.PropertyChanged
#End Region
This interface only defines a single event. This event should be raised whenever the data is changed.
Since every business object will have unique requirements for the save operation, the SaveItem method is not implemented. Rather it is defined as abstract.
In C#:
public abstract Boolean SaveItem();
In VB:
Public MustOverride Function SaveItem() As Boolean
Defining an abstract (MustOverride in VB) SaveItem method ensures that every business object has a SaveItem method.
Finally, since the EntityState property setter is private, the base class needs a method to set the entity state.
In C#:
protected internal void SetEntityState(EntityStateType newEntityState)
{
switch (newEntityState)
{
case EntityStateType.Deleted:
case EntityStateType.Unchanged:
case EntityStateType.Added:
this.EntityState = newEntityState;
break;
default:
if (this.EntityState == EntityStateType.Unchanged)
this.EntityState = newEntityState;
break;
}
}
protected internal void SetEntityState(EntityStateType newEntityState, string propertyName)
{
SetEntityState(newEntityState);
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
In VB:
Protected Friend Sub SetEntityState(ByVal dataState As EntityStateType)
SetEntityState(dataState, Nothing)
End Sub
Protected Friend Sub SetEntityState( _
ByVal newEntityState As EntityStateType, ByVal propertyName As String)
Select Case newEntityState
Case EntityStateType.Deleted, _
EntityStateType.Unchanged, _
EntityStateType.Added
Me.EntityState = newEntityState
Case Else
If Me.EntityState = EntityStateType.Unchanged Then
Me.EntityState = newEntityState
End If
End Select
If Not String.IsNullOrEmpty(propertyName) Then
Dim e As New PropertyChangedEventArgs(propertyName)
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
The SetEntityState method has two overloads. The first is used when changing the entity state in general and the second is used when changing the entity state because a specific property is changed.
For example, when setting an object as Unchanged, Added, or Deleted, it does not matter which property was changed. But when a particular property is changed, the code must also raise the PropertyChanged event.
In this case, the C# and VB code was implemented differently. In the C# code, the code to set the entity state is in the first overload. The second overload then calls the first and then raises the event.
In the VB code, the first overload simply calls the second overload. The second overload then sets the entity state and then raises the event as appropriate. You can use either technique in either language.
That’s it! You now have a base class that can be used with any business object class. If you have any other common functionality, you can add it to this base class.
Here is an example of how you use this base class.
In C#:
public class Customer : BoBase
{
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (_LastName == null || _LastName != value)
{
string propertyName = "LastName";
_LastName = value;
// Validate the last name
ValidationInstance.ValidateClear(propertyName);
ValidationInstance.ValidateRequired(propertyName, value);
SetEntityState(EntityStateType.Modified, propertyName);
}
}
}
public override Boolean SaveItem()
{
// TODO: Add code here
}
}
In VB:
Public Class Customer
Inherits BoBase
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
If _LastName Is Nothing OrElse _LastName <> value Then
Dim propertyName As String = "LastName"
_LastName = value
' Validate the last name
ValidationInstance.ValidateClear(propertyName)
ValidationInstance.ValidateRequired(propertyName, value)
SetEntityState(EntityStateType.Modified, propertyName)
End If
End Set
End Property
Public Overrides Function SaveItem() As Boolean
' TODO: Add code here
End Function
End Class
Notice how the class statement includes the syntax to inherit from BoBase. The LastName property uses the ValidationInstance defined in the base class to validate the value. It also sets the entity state when the last name is changed.
Enjoy!
A common requirement in most applications is to validate the data entered by the user. This is such a common requirement, that it makes sense to build a reusable Validation class. This post details the beginnings of a .NET Validation class.
The class was originally designed to validate business object properties. But it could also be used to directly validate fields in your user interface if you are not yet using business objects.
In C#:
using System;
using System.Collections.Generic;
using System.Text;
namespace InStepLibrary
{
public class Validation
{
}
}
In VB:
Imports System.Text
Public Class Validation
End Class
This class uses a StringBuilder, hence the need for the System.Text imports.
The Validation class has three properties.
In C#:
public int Count
{
get
{
return ValidationList.Count;
}
}
public string this[string propertyName]
{
get
{
if (ValidationList.ContainsKey(propertyName))
return ValidationList[propertyName];
else
return null;
}
}
private Dictionary<String, String> ValidationList { get; set; }
In VB:
Public ReadOnly Property Count() As Integer
Get
Return ValidationList.Count
End Get
End Property
Public ReadOnly Property Item(ByVal propertyName As String) As String
Get
If ValidationList.ContainsKey(propertyName) Then
Return ValidationList.Item(propertyName)
Else
Return Nothing
End If
End Get
End Property
Private _ValidationList As Dictionary(Of String, String)
Private Property ValidationList() As Dictionary(Of String, String)
Get
Return _ValidationList
End Get
Set(ByVal value As Dictionary(Of String, String))
_ValidationList = value
End Set
End Property
The ValidationList property retains a private Dictionary of validation errors. The key of the dictionary is the property name and the value is the error text. For example, the “LastName” property may have a validation error such as “Last Name is required.”
The Count property returns the number of properties with validation errors by returning the count of the ValidationList items.
The Item property provides access to the validation errors given a property name. This property is implemented as the class indexer in C#.
The constructor ensures that a new instance of the ValidationList Dictionary is created.
In C#:
public Validation()
{
// Create the list to contain the validation errors
ValidationList = new Dictionary<String, String>();
}
In VB:
Public Sub New()
' Create the list to contain the validation errors
ValidationList = New Dictionary(Of String, String)
End Sub
The ToString method is overwritten in the Validation class to build a single string containing all of the validation errors.
In C#:
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (string k in ValidationList.Values)
sb.AppendLine(k + ": " + ValidationList[k]);
return sb.ToString();
}
In VB:
Public Overrides Function ToString() As String
Dim sb As New StringBuilder
For Each k As String In ValidationList.Keys
sb.AppendLine(k & ": " & ValidationList(k))
Next
Return sb.ToString
End Function
This method uses the StringBuilder class to build up the potentially large string of errors.
When a validation error is added to the list, it is added for a particular property. If the property already has a validation error, additional validation errors are appended to it, separated by semi-colons (;). An AddValidationError method handles this logic.
In C#:
private void AddValidationError(string propertyName, string message)
{
// If the property already has a message, append this message
if (ValidationList.ContainsKey(propertyName))
{
string existingMessage = ValidationList[propertyName];
if (!existingMessage.Contains(message))
// Append the new message to the existing message
ValidationList[propertyName] += "; " + message;
}
else
// Add the message to the validation list
ValidationList.Add(propertyName, message);
}
In VB:
Private Sub AddValidationError(ByVal propertyName As String, _
ByVal message As String)
' If the property already has a message, append this message
If ValidationList.ContainsKey(propertyName) Then
Dim existingMessage As String = ValidationList(propertyName)
If Not existingMessage.Contains(message) Then
' Append the new message to the existing message
ValidationList(propertyName) &= "; " & message
End If
Else
' Add the message to the validation list
ValidationList.Add(propertyName, message)
End If
End Sub
A ValidateClear method clears any existing validation errors for a property. This method should be called before performing any new validation on the property. For example, the user leaves the Last Name field empty. The validation is performed and a validation error entry is created in the ValidationList. Then the user enters a value into the last name field. The original validation error must be cleared before revalidating the value.
In C#:
public void ValidateClear(string propertyName)
{
// If the Property doesn't have any messages, this is done
if (ValidationList.ContainsKey(propertyName))
// Otherwise, remove the entry
ValidationList.Remove(propertyName);
}
In VB:
Public Sub ValidateClear(ByVal propertyName As String)
' If the Property doesn't have any messages, this is done
If ValidationList.ContainsKey(propertyName) Then
' Otherwise, remove the entry
ValidationList.Remove(propertyName)
End If
End Sub
The ValidateClear method uses the Remove method of the Dictionary to remove any Dictionary entry for the property.
That’s it for the basics of the Validation class. The only thing that is left to do is build all of the methods to perform the types of validation that your application requires.
For example, a common requirement is to ensure that a property is not left blank.
In C#:
public bool ValidateRequired(string propertyName,
string value)
{
string newMessage = String.Empty;
if (String.IsNullOrEmpty(value))
{
newMessage =
String.Format("{0} is required, please enter a valid value",
propertyName);
// Add the message to the validation list
AddValidationError(propertyName, newMessage);
return false;
}
else
return true;
}
In VB:
Public Function ValidateRequired(ByVal propertyName As String, _
ByVal value As String) As Boolean
Dim newMessage As String = String.Empty
If String.IsNullOrEmpty(value) Then
newMessage = _
String.Format("{0} is required. Please enter a valid value.", _
propertyName)
' Add the message to the validation list
AddValidationError(propertyName, validationMessage)
Return False
Else
Return True
End If
End Function
This function performs the validation, adding a validation error as appropriate.
Another common requirement is that a property not exceed a maximum length.
In C#:
public Boolean ValidateLength(string propertyName,
string value, int maxLength)
{
String newMessage = String.Empty;
if (!String.IsNullOrEmpty(value) && value.Length > maxLength)
{
newMessage = String.Format("{0} has a maximum size of {1}",
propertyName, maxLength);
// Add the message to the validation list
AddValidationError(propertyName, newMessage);
return false;
}
else
return true;
}
In VB:
Public Function ValidateLength(ByVal propertyName As String, _
ByVal value As String, ByVal maxLength As Integer) As Boolean
Dim sMessage As String = String.Empty
If Not String.IsNullOrEmpty(value) AndAlso _
value.Length > maxLength Then
sMessage = String.Format("{0} has a maximum size of {1}.", _
propertyName, maxLength)
' Add the message to the validation list
AddValidationError(propertyName, sMessage)
Return False
Else
Return True
End If
End Function
You get the idea. You can add any number of these to perform whatever validation your application requires.
Some additional suggestions:
- ValidateAlphaNumeric
- ValidateDirectory
- ValidateEnum
- ValidateFileExists
- ValidateMinLength
- ValidateNonZero
- ValidateNoSpaces
- ValidateNumeric
You can create any validation method you need for your application. Just add code to perform the validation and then call AddValidationError as appropriate.
You call these validation methods from your business objects as shown below.
NOTE: This code assume you have a ValidationInstance variable that is declared in your class as a new instance of the Validation class.
In C#:
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (_LastName == null || _LastName != value)
{
string propertyName = "LastName";
_LastName = value;
// Validate the last name
ValidationInstance.ValidateClear(propertyName);
ValidationInstance.ValidateRequired(propertyName, value);
}
}
}
In VB:
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
If _LastName Is Nothing OrElse _LastName <> value Then
Dim propertyName As String = "LastName"
_LastName = value
' Validate the last name
ValidationInstance.ValidateClear(propertyName)
ValidationInstance.ValidateRequired(propertyName, value)
End If
End Set
End Property
Or you can call these validation methods directly from your user interface as shown below.
NOTE: This code assume you have a ValidationInstance variable that is declared in your form as a new instance of the Validation class.
In C#:
private void textBox1_Validating(object sender, CancelEventArgs e)
{
string propertyName = "Last Name";
validationInstance.ValidateClear(propertyName);
validationInstance.ValidateRequired(propertyName, textBox1.Text);
validationInstance.ValidateLength(propertyName, textBox1.Text, 20);
if (validationInstance.Count > 0)
errorProvider1.SetError(textBox1,
validationInstance[propertyName]);
else
// Clear the validation error
errorProvider1.SetError(textBox1, String.Empty);
}
In VB:
Private Sub TextBox1_Validating(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs) _
Handles TextBox1.Validating
Dim propertyName As String = "Last Name"
validationInstance.ValidateClear(propertyName)
validationInstance.ValidateRequired(propertyName, TextBox1.Text)
validationInstance.ValidateLength(propertyName, TextBox1.Text, 20)
If validationInstance.Count > 0 Then
ErrorProvider1.SetError(TextBox1, _
validationInstance.Item(propertyName))
Else
' Clear the validation error
ErrorProvider1.SetError(TextBox1, String.Empty)
End If
End Sub
Hope this helps you encapsulate all of your validation logic into a reusable class.
Enjoy!
Being a physics major in college, I am familiar with the Heisenberg Uncertainty Principle which basically states that in quantum physics, some properties (like position and momentum) cannot both be known to any precision.
Though not technically accurate, the Heisenberg Uncertainty Principle is often simplified to what is called the observer effect. This refers to the fact that the act of observing something can change its behavior.
This is very often true in programming, especially when it comes to debugging. The act of debugging an application can alter how the application behaves. Due to the comingling of the meaning of the Heisenberg Uncertainty Principle and the observer effect, the types of bugs that disappear or are altered by observing them are sometimes called heisenbugs.
Ever try debugging drag and drop operations? How about mouse movements?
Have you found any heisenbugs in your code? Want to share them? Post your thoughts using the comments.
Thanks!
My prior post demonstrated how to bind to a list of month names. Once the user picks the desired month, you may want to provide a list of valid dates to pick a date in that month. For example: 1-30 for September and 1-31 for July.
You can accomplish this using a switch statement (Select Case in VB), but that hard-codes the dates, does not handle leap year, and does not support localization.
Another option is to use the culture specific calendar as shown below.
The code is first shown in both VB and C#. It is then described in detail below.
In C#:
private List<int> GetListOfDays(int yearNumber , int monthNumber)
{
Calendar currentCalendar = CultureInfo.CurrentCulture.Calendar;
int numberOfDays =
currentCalendar.GetDaysInMonth(yearNumber, monthNumber);
return Enumerable.Range(1, numberOfDays).ToList();
}
In VB:
Private Function GetListOfDays(ByVal yearNumber As Integer, _
ByVal monthNumber As Integer) As List(Of Integer)
Dim currentCalendar As Calendar = CultureInfo.CurrentCulture.Calendar
Dim numberOfDays As Integer = _
currentCalendar.GetDaysInMonth(yearNumber, monthNumber)
Return Enumerable.Range(1, numberOfDays).ToList()
End Function
This function first defines the calendar based on the current culture. It then uses the calendar’s GetDaysInMonth method to get the appropriate number of days in the month.
It then uses the Range method of Enumerable to build a list of numbers from 1 to the number of days in the month and returns it as a list of integers.
This method is called as follows:
In C#:
comboBox1.DataSource = GetListOfDays(2009, 2);
In VB:
ComboBox1.DataSource = GetListOfDays(2009, 2)
Enjoy!
Visual Studio comes with DateTimePicker and MonthCalendar controls that provide a standard looking calendar for the user to pick a date. But there are times when these controls don’t provide the features you need.
For example, say you want to ask the user for the month and year that their credit card expires. Or you want to ask for a birth date and don’t want the user to have to scroll back to 1932. Or better yet, don’t need to know the year (not that I refuse to admit my birth year <G>.)
There is an easy way to achieve this.
The code is first shown in both VB and C#. It is then described in detail below.
In C#:
string[] MonthNames=
CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
comboBox1.DataSource = MonthNames;
In VB:
Dim MonthNames As String() = _
CultureInfo.CurrentCulture.DateTimeFormat.MonthNames
ComboBox1.DataSource = MonthNames
This code gets the month names based on the current culture. This ensures that you get the correct localized names. You can then bind the array to a ComboBox or ListBox.
NOTE: In both cases, be sure to set a reference and import System.Globalization.
When looking at the list from the US (and most countries), there is a problem. There appears to be room for 13 values in the list. This is for those countries that have 13 months, such as Ethiopia.
So for most countries, there is a blank entry at the bottom of the list when it is bound. There are numerous ways of removing the empty entry. Even though it might not be the most performant, I like lambda expressions, so here is one way to remove the empty entry.
In C#:
comboBox1.DataSource =
MonthNames.Where(m => !string.IsNullOrEmpty(m)).ToList();
In VB:
ComboBox1.DataSource = _
MonthNames.Where(Function(m) Not String.IsNullOrEmpty(m)).ToList()
Enjoy!
The Enum keyword allows you to define a standard set of named constants for use in your application. Sometimes you may want to present this same list of values to your user.
You can display the set of Enum values in a ComboBox or ListBox using data binding. And even better, if you use the DescriptionAttribute in the Enum, you can display a more user-friendly set of values.
For example, a Customer business object may have a CustomerType defined with a specific set of options for corporations, individuals, schools, and charities. You want to use these values in your code, but also display them to the user in a ComboBox.
Enum Definition
The Enum can reside in the Customer class, or in its own class.
In C#:
public enum CustomerTypeOption
{
NotDefined,
Corp,
IndividualorFamily,
SchoolorUniversity,
Charity
}
In VB:
Public Enum CustomerTypeOption
NotDefined
Corp
IndividualorFamily
SchoolorUniversity
Charity
End Enum
CustomerType Property
A CustomerType property in the Customer class can use this enum type.
In C#:
public CustomerTypeOption CustomerType { get; set; }
In VB:
Private _CustomerType As CustomerTypeOption
Public Property CustomerType() As CustomerTypeOption
Get
Return _CustomerType
End Get
Set(ByVal value As CustomerTypeOption)
_CustomerType = value
End Set
End Property
The C# code uses the automatically implemented properties feature introduced with .NET 3.5. The VB code uses the full property definition.
Binding To an Enum
The Enum can also be used by the user interface. For example, you can bind a comboBox or listBox to a set of customer type options.
In C#:
Dictionary<string, int> CustomerTypeList =
new Dictionary<string, int>();
foreach (int enumValue in
Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
CustomerTypeList.Add(
Enum.GetName(typeof(Customer.CustomerTypeOption),
enumValue ), enumValue);
}
// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList,
null);
In VB:
Dim CustomerTypeList As New Dictionary(Of String, Integer)
For Each enumValue As Integer In _
[Enum].GetValues(GetType(Customer.CustomerTypeOption))
CustomerTypeList.Add( _
[Enum].GetName(GetType(Customer.CustomerTypeOption), _
enumValue), enumValue)
Next
' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
Nothing)
This code defines a Dictionary to contain the name of the enum and the integer value of the enum. The for/each loop iterates through the set of Enum values and adds the name and value to the dictionary.
The code then binds to the ComboBox, setting the DisplayMember to the “Key” of the dictionary and the ValueMember to the “Value” of the Dictionary.
The result looks like this:
While it does provide the appropriate options, it is not very user-friendly. It would be nicer if there were appropriate spaces and full words. For that, you can use the DescriptionAttribute on the Enum.
Using the DescriptionAttribute
NOTE: Be sure to import the System.ComponentModel and the System.Reflection namespaces.
In C#:
public enum CustomerTypeOption
{
[DescriptionAttribute("Not Specified")]
NotDefined,
[DescriptionAttribute("Corporation")]
Corp,
[DescriptionAttribute("Individual or Family")]
IndividualorFamily,
[DescriptionAttribute("School or University")]
SchoolorUniversity,
[DescriptionAttribute("Charity")]
Charity
}
In VB:
Public Enum CustomerType
<DescriptionAttribute("Corporate Customer")> _
CorpCustomer
<DescriptionAttribute("Individual or Family")> _
IndividualorFamily
<DescriptionAttribute("School or University")> _
SchoolorUniversity
<DescriptionAttribute("Charity")> _
Charity
<DescriptionAttribute("Kingdom")> _
Kingdom
End Enum
Binding to an Enum Description
Now you can bind to the description instead of to the Enum constant.
In C#:
Dictionary<string, int> CustomerTypeList =
new Dictionary<string, int>();
FieldInfo fi;
DescriptionAttribute da;
foreach (Customer.CustomerTypeOption enumValue in
Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
fi = typeof(Customer.CustomerTypeOption).
GetField((enumValue.ToString()));
da = (DescriptionAttribute)Attribute.GetCustomAttribute(fi,
typeof(DescriptionAttribute));
if (da != null)
{
CustomerTypeList.Add(da.Description, (int)enumValue);
}
}
// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList,
null);
In VB:
Dim CustomerTypeList As New Dictionary(Of String, Integer)
Dim fi As FieldInfo
Dim da As DescriptionAttribute
For Each enumValue As Customer.CustomerTypeOption In _
[Enum].GetValues(GetType(Customer.CustomerTypeOption))
fi = GetType(Customer.CustomerTypeOption). _
GetField(enumValue.ToString)
da = DirectCast(Attribute.GetCustomAttribute(fi, _
GetType(DescriptionAttribute)), DescriptionAttribute)
If da IsNot Nothing Then
CustomerTypeList.Add(da.Description, enumValue)
End If
Next
' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
Nothing)
This code defines a Dictionary to contain the description of the enum and the integer value of the enum. The for/each loop iterates through the set of Enum values. It uses reflection to find the DescriptionAttribute. It then adds the description and value to the dictionary.
The code then binds to the ComboBox, setting the DisplayMember to the “Key” of the dictionary and the ValueMember to the “Value” of the Dictionary.
Now the combo box looks like this:
Much better!
Extension Methods
So the user interface looks nicer, but the code to get the DescriptionAttribute looks rather tedious. It would be much nicer if there was a GetDescription method on the Enum. But hey, with the extension method feature introduced in .NET 3.5, we can build one ourselves!
In C#:
public static string GetDescription(this Enum currentEnum)
{
string description = String.Empty;
DescriptionAttribute da ;
FieldInfo fi = currentEnum.GetType().
GetField(currentEnum.ToString());
da = (DescriptionAttribute)Attribute.GetCustomAttribute(fi,
typeof(DescriptionAttribute));
if (da != null)
description = da.Description;
else
description = currentEnum.ToString();
return description;
}
In VB:
<ExtensionAttribute()> _
Public Function GetDescription(ByVal currentEnum As [Enum]) As String
Dim description As String = String.Empty
Dim da As DescriptionAttribute
Dim fi As FieldInfo = currentEnum.GetType. _
GetField(currentEnum.ToString)
da = DirectCast(Attribute.GetCustomAttribute(fi, _
GetType(DescriptionAttribute)), DescriptionAttribute)
If da IsNot Nothing Then
description = da.Description
Else
description = currentEnum.ToString
End If
Return description
End Function
The C# code can reside in any static class. The VB code must reside in a module, which provides the same features as a static class.
This code uses reflection to get the DescriptionAttribute for any Enum value. So it can be readily reused. The code returns the DescriptionAttribute if one is found, otherwise it returns the name.
Binding to an Enum Description - Redux
Now the binding code is much easier to work with.
In C#:
Dictionary<string, int> CustomerTypeList =
new Dictionary<string, int>();
foreach (Customer.CustomerTypeOption enumValue in
Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
CustomerTypeList.Add(enumValue.GetDescription(), (int)enumValue);
}
// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList,
null);
In VB:
Dim CustomerTypeList As New Dictionary(Of String, Integer)
For Each enumValue As Customer.CustomerTypeOption In _
[Enum].GetValues(GetType(Customer.CustomerTypeOption))
CustomerTypeList.Add(enumValue.GetDescription, enumValue)
Next
' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
Nothing)
Enjoy!
Most business applications have business objects such as customer, order, or invoice. Often, the data access layer (DAL) provides the data and your code needs to use that data to manually populate a business object.
This post describes how to manually populate a business object from a DataTable. It uses the Customer class defined here.
The code is first shown in both VB and C#. It is then described in detail below.
In C#:
public static List<Customer> Retrieve()
{
DataTable dt = Dac.ExecuteDataTable("CustomerRetrieveAll", null);
List<Customer> customerList = new List<Customer>();
foreach (DataRow dr in dt.Rows)
{
customerList.Add(new Customer()
{
CustomerId = (int)dr["CustomerId"],
LastName = (string)dr["LastName"],
FirstName = (string)dr["FirstName"]
});
}
return customerList;
}
In VB:
Public Shared Function Retrieve() As List(Of Customer)
Dim dt As DataTable = Dac.ExecuteDataTable( _
"CustomerRetrieveAll", nothing)
Dim customerList As New List(Of Customer)
For Each dr As DataRow In dt.Rows
customerList.Add(New Customer With _
{.CustomerId = CType(dr("CustomerID"), Integer), _
.LastName = dr("LastName").ToString, _
.FirstName = dr("FirstName").ToString})
Next
Return customerList
End Function
The Retrieve method is public and static (shared in VB). It is public so it can be called from the user interface code. It is static/shared because it does not use or retain any state. This allows the function to be called without creating an instance of the class containing the function.
This function calls the ExecuteDataTable method from here, which returns a DataTable. It then creates a new list and adds a new customer to the list for each row in the DataTable.
The resulting list of customers is returned. The user interface code can then use this list to bind to a control such as a grid or combo box. You can also search, sort, filter, or work with this list using LINQ or Lambda expressions.
NOTE: If the fields in the DataTable match the properties of the business object, you could use reflection to map the fields to the properties. Though reflection does have a performance hit, it provides a more general solution that could be used by any business object.
Enjoy!
More Posts
Next page »