August 2009 - Posts
Humans like to classify things, to find similarities in things, and to group
them accordingly. Things with similar attributes (properties) and behaviors
(methods) are grouped. In object-oriented terminology, the definition of
the properties and methods that describe a particular classification is called
a class. A class defines a particular object type. You can think of a class as a
blueprint, providing the details of how objects of that type are made.
[To begin with an overview of OO, start here.]
A cookie cutter is another analogy often used to illustrate the relationship
between objects and a class. The cookie cutter is the class and
provides the definition; it specifies the size and shape. The cookies are the
objects created from the cookie cutter class.
For example, an employee class can have name, address, and occupation
properties. Sam Smith and Jessica Jones are both objects from the
employee class. Likewise, a time sheet class can have employee, date, and hours properties and a calculate pay method. Each person’s weekly time
sheet is then an object from the time sheet class.
A specific object created from a class is called an instance of the
class. Each instance can have values for the defined set of properties and
can perform the defined methods. So Sam is an instance of the employee
class, and Jessica’s time sheet for last week is an instance of the time sheet
class.
The set of properties and methods described by a class are often called
class members. A class defines the members, including the actual code to
maintain the property values and perform the methods. Each object that
is an instance of that class can execute the methods and retain values for
each property.
A class itself normally does not maintain state, meaning that it normally
does not have any property values. Nor does it normally execute the class
methods. Instead, the class defines the properties and contains the implementation of the methods that are used by each object created from the
class. Each object has values for the properties and performs the methods.
You do not eat the cookie cutter or fill out a time sheet class. You eat each
cookie and fill out each individual time sheet.
The .NET Framework itself is composed of a set of classes. For
example, the button in the Visual Studio Toolbox represents a Button
class. Each time you add a button to a form, you create an instance of
that Button class. The Button class has specific defined methods, such
as Focus, and properties, such as Name and Text. The Button class
itself does not have a value for the Name or Text properties. Nor can it
have focus. Instead, it contains the implementation of the Name and Text
properties and the Focus method. The Button objects that you create as
instances of that Button class have values for the Name and Text properties
and perform the Focus method.
Code you write normally resides in a class. If you type some
code in a form, your code is in the form’s class. Even if you write code in a
Visual Basic module, the code compiles as a class!
If you have never created a class, the following example may help you
visualize the code for a TimeSheet class.
In C#:
public class TimeSheet
{
public Employee CurrentEmployee { get; set; }
public decimal TotalHours { get; set; }
private DateTime _WeekEndingDate;
public DateTime WeekEndingDate
{ get
{ return _WeekEndingDate; }
set
{
if (value > DateTime.Now.Date)
throw new Exception(
"Date must be on or before today's date");
_WeekEndingDate = value;
}
}
public decimal CalculatePay()
{
return CurrentEmployee.HourlyRate * TotalHours;
}
}
In VB:
Public Class TimeSheet
Private _CurrentEmployee As Employee
Public Property CurrentEmployee() As Employee
Get
Return _CurrentEmployee
End Get
Set(ByVal value As Employee)
_CurrentEmployee = value
End Set
End Property
Private _TotalHours As Decimal
Public Property TotalHours() As Decimal
Get
Return _TotalHours
End Get
Set(ByVal value As Decimal)
_TotalHours = value
End Set
End Property
Private _WeekEndingDate As DateTime
Public Property WeekEndingDate() As DateTime
Get
Return _WeekEndingDate
End Get
Set(ByVal value As DateTime)
If value > Now.Date Then
Throw New Exception( _
"Date must be on or before today's date")
End If
_WeekEndingDate = value
End Set
End Property
Public Function CalculatePay() As Decimal
Return CurrentEmployee.HourlyRate * TotalHours
End Function
End Class
The first line defines the class’s scope and name. In this case, the class
is named TimeSheet and is public.
The next set of code defines the private data, called backing fields, and public accessors that provide access to that data. This follows the concept of encapsulation. For example, the private _WeekEndingDate backing field variable contains the value of the time period property. Since this variable is private, it is hidden and cannot be accessed except from within this class. The public WeekEndingDate Property statement implements the get time period and set time period functionality. Any code that needs to set or get the time period uses this public property.
NOTE: The C# code uses auto-implemented properties for the TotalHours and CurrentEmployee. Auto-implemented properties are a short-cut for defining a backing field with its associated accessor. The compiler defines the private backing field for you. You can use auto-implemented properties any time you don't need extra code, such as validation, in your property statement. (VB is getting auto-implemented properties in VB 10 that is coming with VS 2010.)
By implementing properties using private backing fields and public
Property statements, you can associate code with the setting or retrieval
of the data value. In the WeekEndingDate example, the code that sets the
date validates the date before assigning it.
The code at the end of the class in the preceding example demonstrates
a method—in this case, a function that calculates the pay. In Visual
Basic, methods are implemented with functions or subroutines. In C# the syntax is the same for both functions and subroutines.
To access the properties and use the methods of a class, you normally
begin by creating an instance of the class and storing a reference to that
instance in an object variable. You then use that object variable to get or
set properties and execute methods on the resulting object.
The following code uses the new keyword to create a new object from
the TimeSheet class. It then sets the properties on the object and executes
the CalculatePay method on the object.
In C#:
TimeSheet ts = new TimeSheet();
decimal totalPay;
ts.CurrentEmployee = Jessica; //Jessica is an Employee object
ts.WeekEndingDate = new DateTime(2009, 8, 20);
ts.TotalHours = 51;
totalPay = ts.CalculatePay();
In VB:
Dim ts As New TimeSheet
Dim totalPay As Decimal
ts.CurrentEmployee = Jessica 'Jessica is an Employee object
ts.WeekEndingDate = #8/20/2009#
ts.TotalHours = 51
totalPay = ts.CalculatePay
The new keyword creates a new instance of the TimeSheet class, and
a reference to that instance is stored in the defined object variable (ts).
Using the object variable and a period, the remainder of the code sets the
object’s properties and calls the method to calculate the pay.
As stated earlier in this section, objects normally maintain state (property
values) and execute methods. The class itself normally does not maintain
state or execute methods. However, .NET does support static
class data and static class methods.
Static class data (also called shared data) is data that the class
retains, independent of any object. Use static class data any time you want
to retain data that is shared between all objects, such as to keep a count of
the total objects created from a class.
A static class method (also called a shared method) is a method
that can be executed independent of any object. Static class methods are
good for utility functions when you don’t need to perform a process on
any particular object. The MessageBox and Debug classes in the .NET
Framework both have static class methods, so you don’t need to create an
instance of the class to use the methods.
NOTE: If you write code in a Visual Basic module instead of a class, all the properties and methods in the module are static. You cannot create an instance from a module.
Building code as individual classes logically separates the code, with
each class defining its own data and implementing its own methods.
Having code organized into logical classes makes finding code for maintenance and enhancements effortless and produces a system that is much easier to extend. It also simplifies testing and debugging, because you can test each class as an independent unit. By using OO, you can manage the complexity of your application.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
Objects are things. People, companies, employees, time sheets, and ledger
entries are all types of objects. In object-oriented terms, the word object
is used to describe one specific thing, such as Sam Smith the carpenter at
3322 Main Street and the May 15th time sheet for Jessica Jones.
[To begin with an overview of OO, start here.]
Objects have data associated with them called properties. Sam Smith
has a name, occupation, and address. The time sheet has an employee
name, time period, and hours worked.
NOTE: In object-oriented literature, properties are sometimes called attributes, resources, or even just data.
Objects also do things. The time sheet is filled out, validated, and
submitted for payment. In real life, we fill out time sheets, validate them,
and submit them for payment. In a computer system, the time sheet can
perform these operations for itself. The things an object can do are defined
with methods.
NOTE: In object-oriented literature, methods are also called behaviors,
services, operations, or responsibilities.
Objects can be real-world things, such as an employee or time sheet.
Objects can be conceptual things, such as an engineering process or payroll.
Objects can also be implementation-specific things, such as forms,
controls, and DataSets. The same object-oriented concepts apply regardless
of whether the object is based on the real world, on a concept, or on
the implementation.
Since objects are fundamental to understanding object orientation,
it is important that you can recognize objects and define their appropriate
properties and methods. So take a moment to think about the things
around you. What objects do you see? How would you define their properties
and methods?
Your phone has properties such as color, volume, and mute. It has
methods such as increase volume, decrease volume, turn on mute, turn off
mute, dial, answer, and transfer. An employee has properties such as name,
address, and occupation. An electronic time sheet has properties such as
employee name, date, and hours. It has methods such as calculate pay.
An object’s property values should not be directly accessed by something outside the object. For example, the time period should not be adjusted except through a get time period method, and the employee name would not be retrieved except through a get employee name method. This concept of hiding the internal data is a part of what is called encapsulation and is a key premise of object-oriented programming.
Every object is of a specific type. For example, the object defined as
Sam Smith the carpenter at 3322 Main Street is an employee object type,
and the May 15th time sheet for Jessica Jones is a time sheet object type.
All the objects of a particular type have the same set of properties and
methods.
From a programming perspective, you can define your own object
types using classes. Or you can use the object types provided in the .NET Framework. In either case, you first create an object of the desired type, and then you can set or get the object’s properties and call its methods.
The following code creates an object using the .NET Framework
Timer object type. It then sets the object’s Interval property to define
how often the timer goes off and executes its Start method to start the
timer.
In C#:
Timer myTimer = new Timer();
myTimer.Interval = 1000;
myTimer.Start();
In VB:
Dim myTimer As New Timer
myTimer.Interval = 1000
myTimer.Start()
The new keyword in the first line of code creates a new Timer object
and assigns a reference to that new object to the myTimer object variable.
To access a property or method of the new Timer object, use the object
variable and a period (.) and then the name of the property or method.
NOTE: When you type the object variable name and a period in Visual Studio,
you see a list of the properties, methods, and events that are appropriate for that object. This demonstrates the List Members feature of Intellisense. Intellisense provides auto-completion and display of class documentation within Visual Studio as you are coding.
Everything in .NET can be accessed as an object: forms, strings,
even integers. Check out this code:
In C#:
int i = new int();
string s;
i = 5;
s = i.ToString();
In VB:
Dim i As New Integer
Dim s As String
i = 5
s = i.ToString
The first line creates a new Integer object and assigns it to i. The variable
i is then assigned a value of 5. The last line calls the ToString method of
the Integer object to convert the number to a string.
NOTE: Even though you can use the new keyword to create integers, it is not
necessary and is not commonly done in practice. It is shown in this example to illustrate that you can work with simple data types as objects.
In .NET, an integer and other primitive data types, such as Boolean
and decimal, are called value types. Value types store and pass their contents
by their actual value. Technically speaking, value types are allocated
either on the stack or inline in a structure. A standard set of value types
are built into the .NET Framework, such as integer and decimal. You can
also create your own value types. You do not need to use the new keyword
for any value type.
More complex data types in .NET, such as strings and arrays, are
reference types. Reference types store and pass their contents as a reference to the value’s memory location. Technically speaking, reference types
are allocated on the memory heap. The .NET Framework provides a set of
built-in reference types, such as string and array. Classes that you create are reference types.
.NET automatically creates a boxed value type for each value type when necessary. Boxed value types allow value types to be accessed like reference types (such as the preceding integer example). In addition, boxed value types
allow you to convert value types to reference types. For example, adding
to the preceding code example, you could assign the integer to an object.
This converts the integer value type to an object reference type.
In C#:
object o;
o = i;
In VB:
Dim o As Object
o = i
This conversion of value types to reference types and vice versa is
called boxing. Boxing has a performance hit, so you want to minimize the
amount of boxing in your application wherever possible.
Variables that are value types each have their own copy of the data.
Therefore, operations on one variable do not affect other variables.
Variables that are reference types, such as object variables, can refer to the
same object. Therefore, operations on one variable can affect the same
object referenced by another variable. Check this out:
In C#:
Timer myTimer = new Timer();
myTimer.Interval = 1000;
Timer myTimer2;
myTimer2 = myTimer;
myTimer2.Interval = 500;
Debug.WriteLine(myTimer.Interval); // Displays 500
Debug.WriteLine(myTimer2.Interval); // Displays 500
In VB:
Dim myTimer As New Timer
myTimer.Interval = 1000
Dim myTimer2 As Timer
myTimer2 = myTimer
myTimer2.Interval = 500
Debug.WriteLine(myTimer.Interval) ' Displays 500
Debug.WriteLine(myTimer2.Interval) ' Displays 500
This code creates a new Timer object, assigns it to the myTimer object
variable, and sets its Interval property to 1000. It then defines a second
myTimer2 object variable, assigns it to the same Timer object, and sets the
Interval property to 500.
Since myTimer and myTimer2 are object variables, they are reference
types. Assigning one object variable to another object variable sets
both variables to reference the same object. Changing the properties
using either object variable makes the changes to the underlying object.
Hence, the sample code results in 500 for both myTimer.Interval and
myTimer2.Interval.
The one exception to this behavior is strings. Even though strings are
a reference type, assigning one string to another string copies the data so
that both strings do not reference the same data. This provides a more
natural use of strings in your application. For example:
In C#:
string employeeName ;
employeeName = "Jessica Jones";
string employeeName2;
employeeName2 = employeeName;
employeeName2 = "Sam Smith";
Debug.WriteLine(employeeName); // Displays "Jessica Jones"
Debug.WriteLine(employeeName2); // Displays "Sam Smith"
In VB:
Dim employeeName As String
employeeName = "Jessica Jones"
Dim employeeName2 As String
employeeName2 = employeeName
employeeName2 = "Sam Smith"
Debug.WriteLine(employeeName) ' Displays "Jessica Jones"
Debug.WriteLine(employeeName2) ' Displays "Sam Smith"
Even though employeeName2 is assigned to employeeName and then
employeeName2 is changed, employeeName remains unchanged. The
sample code results in the display of Jessica Jones and then Sam
Smith.
Notice also that the New keyword is not required for strings. Strings
were designed as a special case to make it easier and more natural to work with string variables.
Understanding how to create an object and call its properties and
methods is crucial to any development with .NET.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
Today’s world of software design and development is all about managing
complexity. Computer-savvy users want more and more features. Software
products, such as Microsoft Word and Excel, set high expectations. The
business environment requires software to react quickly to shifting corporate
needs. And tools and technologies are changing faster than ever. It is
easy to become overwhelmed by the complexity.
The key to successful software is managing this complexity—and managing
complexity is one of the goals of object orientation (OO). Object-oriented
means looking at a software system in terms of the things, or
objects, that are relevant to that system and how those objects interact. As
you design and then build your application, you can focus on one object at
a time, temporarily ignoring the complexities of the rest of the system.
OO concepts are used in many professions. For example, when
designing an office, an architect thinks about working spaces, foundations,
frameworks, and plumbing systems. These are the real-world objects. The
architect does not concentrate on the process of pouring the foundation,
hammering nails, or connecting the plumbing, nor on the details of the
data, such as how much concrete or how many nails. These lower-level
processes and details are important but not applicable to the high-level
design of an office building. And without the high-level design, the processes
and data details are irrelevant.
Object orientation does not ignore the data or the process. It combines
the best of a procedure-oriented view (where the focus is on the
process) and a data-centric view (where the focus is on the data) and adds
productivity concepts such as reuse, testability, and, of course, managing
complexity.
Consider a time sheet. Using a data-centric view, the key data elements
are the employee name, date, and hours worked. But just looking at the
data does not provide the full picture of time sheet processing. Using a
procedure-oriented view, the focus is on the process of generating the time
sheet. But this does not consider the bigger picture of how the time sheet
fits into an overall system.
From an object-oriented perspective, the time sheet has data (called
properties) and processes (called methods). It also has relationships to
other objects in the system, such as an employee object, a logging object,
a data access object, and so on.
Thinking about an application in an object-oriented way makes it
easier to break the application into its parts (objects), focus on the most
important aspects of each part, and look at the relationships between those
parts. And since Visual Basic is now a fully object-oriented programming
language, using an object-oriented approach to thinking about your application makes it easier to map these thoughts into object-oriented code.
Excerpt from "Doing Objects in Visual Basic 2005".
For more information on using object-oriented techniques, see the following links:
Enjoy!
One of the common requirements in working with Microsoft Word from .NET is to bold some text. This is often required to draw attention to specific words within the document.
This code example highlights any instances of the word "was". It can be changed to instead highlight any word you desire.
In C#:
// Add to the top of the code file
using Word = Microsoft.Office.Interop.Word;
// Add to a subroutine
Word.Application Wd;
Word.Document doc;
object missingValue = Missing.Value;
// Start Word and get Application object
Wd = new Word.Application();
// Add a new document
doc = Wd.Documents.Add(ref missingValue,ref missingValue,
ref missingValue,ref missingValue );
// Write some text
Wd.Selection.TypeText("Once upon a time there was a document. " +
"The document was fair and fine." +
"The document was short.");
// Bold the specified word
foreach (Word.Range w in doc.Words)
{
if (w.Text.Trim() == "was")
w.Font.Bold = 1;
}
// Save
object fileName = @"test1.docx";
doc.SaveAs(ref fileName,
ref missingValue, ref missingValue,
ref missingValue, ref missingValue,
ref missingValue, ref missingValue,
ref missingValue, ref missingValue,
ref missingValue, ref missingValue,
ref missingValue, ref missingValue,
ref missingValue, ref missingValue,
ref missingValue);
// Close
doc.Close(ref missingValue, ref missingValue, ref missingValue);
doc = null;
Wd.Quit(ref missingValue, ref missingValue, ref missingValue);
// Clean up
// NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect() ;
In VB:
' Add to the top of the code file
Imports Word = Microsoft.Office.Interop.Word
' Add to a subroutine
Dim Wd As Word.Application
Dim doc As Word.Document
' Start Word and get Application object
Wd = New Word.Application
' Add a new document
doc = Wd.Documents.Add
' Write some text
Wd.Selection.TypeText("Once upon a time there was a document. " & _
"The document was fair and fine." & _
"The document was short.")
' Bold the specified word
For Each w As Word.Range In doc.Words
If (w.Text.Trim() = "was") Then
w.Font.Bold = 1
End If
Next
' Save
doc.SaveAs("test1.docx")
' Close
doc.Close()
doc = Nothing
Wd.Quit()
' Clean up
' NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
In both of these examples, the code starts Word, creates a new Word document, and writes some text into the document. It then loops through the words in the document and bolds any that match "was". You can, of course, change this to any word.
The code then saves the document. Since no directory was found, it defaults to the My Document folder.
Notice the missingValue variable in the C# code that is not in the VB code. VB supports default parameters, but C# does not. So any time a parameter is defined for a Word method, C# must provide it. VB will use the default parameter values.
NOTE: A new feature in C# 4.0 (Visual Studio 2010) allows for default parameters in C# as well, dramatically simplifying the C# code that interacts with Word or Excel.
Enjoy!
A code review is a formal process of reviewing source code with the goal of finding and fixing errors and improving coding techniques. It often results in both improved software quality and in enhanced developer skills.
As part of my consulting services, many software developers and software development teams have hired me to perform code reviews. Whether you implement an internal code review process or hire a consultant, this post details the primary benefits of code reviews.
1. Enhancing Developer Skills
When we were in school, everything we did was reviewed. Every paper we wrote was reviewed and marked up with suggestions to improve our writing skills. Every Math or Computer Science assignment was reviewed and we were given feedback on needed corrections or improvements. All of this review helped us to learn and get better at what we were doing.
This process all but stops when we enter the working world. Sure there are ways we can enhance our development skills by reading blogs and other Web sites, attending seminars, and following the forums. But a code review can provide a much more thorough review of our coding techniques with much more focused suggestions on enhancing our skills.
Say you see this code during a code review:
In C#:
List<int> numberList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
In VB:
Dim numberList As New List(Of Integer)
numberList.Add(1)
numberList.Add(2)
numberList.Add(3)
numberList.Add(4)
numberList.Add(5)
numberList.Add(6)
numberList.Add(7)
numberList.Add(8)
numberList.Add(9)
You recommend to the developer that the Enumerable class Range method could be used instead:
In C#:
List<int> numberList2 = Enumerable.Range(1, 9).ToList();
In VB:
Dim numberList2 As List(Of Integer) = Enumerable.Range(1, 9).ToList
The team has learned a new technique that will save them time and prevent errors (especially saving typing in the VB case).
2. Learning New Techniques
While the prior topic focused on improving the skills of the reviewee, this benefit focuses on the rest of the team.
It may be that the reviewee used some new technique in their code and shares it with the team during the review process. Or it may be that the reviewer suggests some new techniques while reviewing the code. Either way, the team benefits.
3. Identifying Design Holes/Errors
As we all know, no design is perfect. And with more teams moving to an iterative/agile process, often times the design is more of a guideline.
The code review process can bring to light any questions on business rules, validation, or other possible design holes.
4. Finding Coding Errors
Like me, I'm sure you have at some point written a document, email, or other text, reviewed it, sent it out, and then found some major typographic error that you did not catch.
Same with your code. It is easy to make an error and not notice it as you review your own code, especially if that typo does not result in a syntax error. Code reviews can catch these types of errors.
5. Enhancing the Application Framework
Most teams have (or should have) an application framework, basically a set of base classes and library functions that all of the application's use. For example, you may have a standard logging class or validation class that you use in every application you develop.
The code review process can help identify where you can make additions or enhancements to your application framework. You may see developers repeating a set of code. This should flag a change to the framework so the developers can call a standard set of code instead of repeating the code each time it is needed.
For example, during the code review, you see this in every form of the application:
In C#:
private void textBox_Enter(object sender,
EventArgs e)
{
TextBox tb = (TextBox)sender;
tb.BackColor = Color.BlanchedAlmond;
}
In VB:
Private Sub TextBox_Enter(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim tb As TextBox = DirectCast(sender, TextBox)
tb.BackColor = Color.BlanchedAlmond
End Sub
With a matching Leave event that resets the color back to the standard color.
This repeated code sends up a flag that you should add this to your base form class and remove it from each form. Then if the users later decide to change the desired background color to Blue, you only have to change it in one place.
6. Improving Code Reuse
Once you have an application framework, it is important for all of the developers on the team to use it to achieve the highest productivity. The code review process can point out where framework classes/methods can be used.
7. Increasing Consistency
If you have a set of programming standards, you can visually inspect that the code meets these standards as part of a code review.
8. Improving Code Quality
Code quality is one of the primary selling points for performing a code review. Improved code is basically the result of all of these other benefits.
By improving the skills of the development team, you improve code quality.
By identifying holes/errors in the design (and fixing them), you improve code quality.
By finding code errors during the review process, you improve code quality.
By building an application framework and using it, you reuse tested code thereby improving code quality.
If you are interested in hiring a consultant to assist with your C# or VB.NET code review, I am offering 20% off for any code review performed between now and October 31, 2009. Just mention this blog for the discount. Contact me at deborahk at insteptech.com or (925)730-1000 for more information.
Enjoy!
The specifics of this class demonstrate how to build an alarm. You can use this class to build an alarm clock application, or to add an alarm feature into your application, similar to the Reminder feature in Outlook.
OR you can just use this class as an example of raising events from a business object or displaying sounds asynchronously.
Build the Alarm class as follows.
In C#:
// Declare a delegate
public delegate void AlarmWentOffHandler(object sender, EventArgs e);
public class Alarm
{
// Declare an event
public event AlarmWentOffHandler AlarmWentOff;
public DateTime? AlarmTime { get; set; }
private Timer AlarmTimer { get; set; }
public int SnoozeInterval { get; set; }
public Alarm() : this(null, 5)
{ }
public Alarm(DateTime? timeForAlarm): this(timeForAlarm, 5)
{ }
public Alarm(DateTime? timeForAlarm, int minutesToSnooze)
{
// Set the properties
AlarmTime = timeForAlarm;
SnoozeInterval = minutesToSnooze;
// Start the timer
AlarmTimer = new Timer() {Interval = 10000,
Enabled = true};
AlarmTimer.Tick += CheckAlarm;
}
public void Snooze()
{
// Reset the alarm by the snooze interval
AlarmTime = DateTime.Now.AddMinutes(SnoozeInterval);
// Reset the timer
AlarmTimer.Enabled = true;
}
protected virtual void OnAlarmWentOff(EventArgs e)
{
if (AlarmWentOff != null)
AlarmWentOff(this, e);
}
private void CheckAlarm(object sender,
EventArgs e)
{
// Check whether alarm time has been reached
if (AlarmTime <= DateTime.Now)
{
// Disable the timer
AlarmTimer.Enabled = false;
// Raise the event
OnAlarmWentOff(new EventArgs());
}
}
}
In VB:
Public Class Alarm
Event AlarmWentOff(ByVal sender As Object, ByVal e As EventArgs)
Private _AlarmTime As DateTime?
Public Property AlarmTime() As DateTime?
Get
Return _AlarmTime
End Get
Set(ByVal value As DateTime?)
_AlarmTime = value
End Set
End Property
Private _AlarmTimer As Timer
Private Property AlarmTimer() As Timer
Get
Return _AlarmTimer
End Get
Set(ByVal value As Timer)
_AlarmTimer = value
End Set
End Property
Private _SnoozeInterval As Integer
Public Property SnoozeInterval() As Integer
Get
Return _SnoozeInterval
End Get
Set(ByVal value As Integer)
_SnoozeInterval = value
End Set
End Property
Public Sub New()
' Set Defaults
Me.New(Nothing, 5)
End Sub
Public Sub New(ByVal timeForAlarm As DateTime?)
' Set Defaults
Me.New(timeForAlarm, 5)
End Sub
Public Sub New(ByVal timeForAlarm As DateTime?, _
ByVal minutesToSnooze As Integer)
' Set the properties
AlarmTime = timeForAlarm
SnoozeInterval = minutesToSnooze
' Start the timer
AlarmTimer = New Timer With {.Interval = 10000, _
.Enabled = True}
AddHandler AlarmTimer.Tick, AddressOf CheckAlarm
End Sub
Public Sub Snooze()
' Reset the alarm by the snooze interval
AlarmTime = Now.AddMinutes(SnoozeInterval)
' Reset the timer
AlarmTimer.Enabled = True
End Sub
Private Sub CheckAlarm(ByVal sender As Object, _
ByVal e As EventArgs)
' Check whether alarm time has been reached
If AlarmTime <= Now Then
' Disable the timer
AlarmTimer.Enabled = False
' Raise the event
RaiseEvent AlarmWentOff(Me, New EventArgs())
End If
End Sub
End Class
The C# code begins by declaring a delegate for its event. This is not required in the VB code since the delegate is handled for you.
Within the class, the C# and VB code declare an event called AlarmWentOff. This is the event that is generated when the alarm goes off.
The class has three properties:
- AlarmTime: Time that the alarm should go off.
- AlarmTimer: A private property that manages the timer for checking the alarm time.
- SnoozeInterval: The alarm can optionally be set to snooze. This is the amount of snooze time in minutes.
The C# code uses auto-implemented properties and the VB code uses the expanded property syntax. (VB is getting auto-implemented properties in VB 10.)
The constructors for the class optionally define the alarm time and minutes to snooze. The constructor then starts the timer that goes off every 10 seconds. (This can be adjusted as desired. Or the timer interval could be defined as a property of this Alarm class.)
The class has one public method: Snooze. The Snooze method adds the snooze interval amount to the AlarmTime to reset the alarm. It also ensures that the timer in enabled.
Each time the timer goes off, the code checks the current time against the alarm time. If the alarm time has been reached, the code disables the timer and raises the AlarmWentOff event.
This class can be used whenever you need to add an alarm, notification, or reminder to your application. Here is an example of how this class is accessed.
NOTE: It uses a .wav file for the alarm and plays it using the built-in SoundPlayer.
Start by adding this to the top of your code file.
In C#:
using System.Media;
In VB:
Imports System.Media
Then add the following code somewhere in your user interface.
In C#:
Alarm myAlarm;
private void SetTheAlarm()
{
// Create an alarm object
myAlarm = new Alarm(DateTime.Now.AddMinutes(1), 1);
myAlarm.AlarmWentOff += AlarmWentOff;
}
private void AlarmWentOff(object sender, EventArgs e)
{
var player = new SoundPlayer("myFile.wav");
player.PlayLooping();
DialogResult replay =
MessageBox.Show("Wake up" +
Environment.NewLine +
"Yes to wake up." +
Environment.NewLine +
"No to snooze.",
"Alarm Clock",
MessageBoxButtons.YesNo,
MessageBoxIcon.Exclamation);
player.Stop();
if (replay != DialogResult.Yes)
myAlarm.Snooze();
}
In VB:
Dim myAlarm As Alarm
Private Sub SetTheAlarm()
' Create an alarm object
myAlarm = New Alarm(Now.AddMinutes(1), 1)
AddHandler myAlarm.AlarmWentOff, AddressOf AlarmWentOff
End Sub
Private Sub AlarmWentOff(ByVal sender As Object, ByVal e As EventArgs)
Dim player As New SoundPlayer("myFile.wav")
player.PlayLooping()
Dim replay As DialogResult = _
MessageBox.Show("Wake up" & _
Environment.NewLine & _
"Yes to wake up." & _
Environment.NewLine & _
"No to snooze.", _
"Alarm Clock", _
MessageBoxButtons.YesNo, _
MessageBoxIcon.Exclamation)
player.Stop()
If replay <> DialogResult.Yes Then
myAlarm.Snooze()
End If
End Sub
This code defines a variable that references the Alarm class. In a SetTheAlarm method, the class creates an instance of the class, passing in an alarm time and snooze interval.
For testing, this is set to 1 minute from the current time and the snooze is set to one minute. In most cases, you would want to get the alarm time from the user.
When the alarm goes off, this code plays a .wav file using the SoundPlayer provided in .NET. Be sure to change the .wav file name to the directory and file name of a .wav file on your system.
The PlayLooping method of the SoundPlayer is used to play the sound continuously. This method is asynchronous, so the code will continue to the next line.
The example displays a message box, allowing the user to cancel the alarm or snooze the alarm. In either cause, the code calls the Stop method of the SoundPlayer to stop the .wav file. If the user requested to snooze, it calls the alarm's Snooze method.
Enjoy!
Despite the fact that there is a free version of SQL Server called SQL Server Express, there are still applications that require using a Microsoft Access database. But since these are dwindling in number, there are few articles or posts devoted to accessing Access.
This post (and my prior post that demonstrates how to retrieve Access data) attempt to rectify that issue.
The code is first shown in both VB and C#. It is then described in detail below.
NOTE: Be sure to set a reference to System.Data.OleDb.
In C#:
string update = "Update Customer Set Title = ? Where PersonId = ?";
string cnnString =
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Customer.mdb;";
using (var cnn = new OleDbConnection(cnnString))
{
cnn.Open();
using (var cmd = new OleDbCommand(update, cnn))
{
// Add the parameters
cmd.Parameters.AddWithValue("@Title", "President");
cmd.Parameters.AddWithValue("@PersonId", 1);
// Execute the command
cmd.ExecuteNonQuery();
}
}
In VB:
Dim update As String = _
"Update Customer Set Title = ? Where PersonId = ?"
Dim cnnString As String = _
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Customer.mdb;"
Using cnn As New OleDbConnection(cnnString)
cnn.Open()
Using cmd As New OleDbCommand(update, cnn)
' Add the parameters
cmd.Parameters.AddWithValue("@Title", "President")
cmd.Parameters.AddWithValue("@PersonId", 1)
' Execute the command
cmd.ExecuteNonQuery()
End Using
End Using
The code begins by declaring several variables. The first variable is the Update statement. Change it to update the desired fields from the desired table. Use "?" as placeholders for command parameters.
The connection string uses the Jet OleDb data provider. Change the connection string Data Source property to the directory and name of your Access database file. If you provide no directory (like in this example), it will assume that the file is located in the same directory as the executing application.
The first using statement defines the connection. The connection is then opened. The connection is automatically closed at the end of the using block.
The second using statement creates the command using the defined query and opened connection. Use the AddWithValue method of the command parameter's collection to add the parameter names and values. There should be an AddWithValue method call for each "?" placeholder in the Update statement.
Finally, execute the command using the ExecuteNonQuery method.
Enjoy!
Despite the fact that there is a free version of SQL Server called SQL Server Express, there are still applications that require using a Microsoft Access database. But since these are dwindling in number, there are few articles or posts devoted to accessing Access.
This post (and my next post that demonstrates how to update Access data)attempt to rectify that issue.
The code is first shown in both VB and C#. It is then described in detail below.
NOTE: Be sure to set a reference to System.Data.OleDb.
In C#:
string query = "Select * from Customer";
string cnnString =
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Customer.mdb;";
DataTable dt = new DataTable("Customer");
using (var cnn = new OleDbConnection(cnnString))
{
cnn.Open();
using (var da = new OleDbDataAdapter())
{
da.SelectCommand = new OleDbCommand(
query, cnn);
// Populate the DataTable
da.Fill(dt);
}
}
In VB:
Dim query As String = "Select * from Customer"
Dim cnnString As String = _
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Customer.mdb;"
Dim dt As DataTable = New DataTable("Customer")
Using cnn As New OleDbConnection(cnnString)
cnn.Open()
Using da As New OleDbDataAdapter()
da.SelectCommand = New OleDbCommand( _
query, cnn)
' Populate the DataTable
da.Fill(dt)
End Using
End Using
The code begins by declaring several variables. The first variable is the Select statement. Change it to select the desired fields from the desired table.
NOTE: Even though Select * was shown in this example, good programming practices dictates specifying field names in the Select clause and not using the Select * syntax.
The connection string uses the Jet OleDb data provider. Change the connection string Data Source property to the directory and name of your Access database file. If you provide no directory (like in this example), it will assume that the file is located in the same directory as the executing application.
The code then creates an instance of the DataTable, giving it a name of Customer. Rename the table as appropriate for your application.
The first using statement defines the connection. The connection is then opened. The connection is automatically closed at the end of the using block.
The second using statement defines a data adapter. The SelectCommand property of the data adapter is set to the defined query and opened connection. The data adapter is then used to fill the DataTable.
Use the technique defined here to display the resulting DataTable.
Enjoy!
In my prior post, I covered how to read a fixed length file into an in-memory DataTable. You could then work with the DataTable as desired to access the fields from the file. You could even bind the resulting DataTable to a grid or other control.
But sometimes you just need to process the file line-by-line and need a simpler solution. That is where the TextFieldParser class comes in.
The TextFieldParser works with any file extension and with any character set, so you can use it with UTF-8 or ANSI text files.
For this example, the text file is as follows:
000001 Baggins Bilbo 20090811
000002 Baggins Frodo 20090801
000003 Gamgee Samwise 20090820
000004 Cotton Rosie 20090821
NOTE: Be sure to set a reference to Microsoft.VisualBasic.FileIO
Since this class is part of the Visual Basic namespace, only the VB code is presented here. However, you can import this namespace in a C# program and use it.
Dim fileName As String = "testFixed.txt"
Dim dirName As String = _
Path.GetDirectoryName(Application.ExecutablePath)
Using tf As New TextFieldParser _
(Path.Combine(dirName, fileName))
tf.TextFieldType = FileIO.FieldType.FixedWidth
tf.SetFieldWidths(6, 22, 10, 8)
Dim row As String()
While Not tf.EndOfData
Try
row = tf.ReadFields()
For Each field As String In row
' Do whatever to the set of fields
Debug.WriteLine(field)
Next
Catch ex As MalformedLineException
MessageBox.Show("Line " & ex.Message & _
"is not valid and will be skipped.")
End Try
End While
End Using
This code first declares variables to hold the text file name and the directory name. The file can reside in any directory that the user can access. In this example, the file resides in the same directory where the application is executed. But this is not a requirement.
The first using statement in the example code creates an instance of the TextFieldParser class. The parameter to the constructor defines the directory and filename of the text file. The Path.Combine method is used to ensure that the correct slashes are added to the end of the directory name as required.
The code then sets two additional properties. The first is TextFieldType. Since this is a fixed width file, the FixedWidth type is specified. The second is SetFieldWidths. This defines the widths of each column in the text file.
The While loop processes each line of the file. The ReadFields method reads each row of the file into a string array. If there is a problem reading the line, the Try/Catch block catches the error, displays a message and continues. (However, in a real application you would want to log this information instead of displaying it to the user.)
The For/Next statement loops through the fields in the row and displays them. This is where you would add the code that processes the values from the text file.
Notice that as you go through each loop, you replace the row variable with the field from the next line. So after a row is processed, you cannot access the data from that row again. (If you do need more random access to the data, you can store each row in a list or other structure, or you can read the data into a DataTable using the technique detailed here.)
The resulting information in the Debug window is as follows:
000001
Baggins
Bilbo
20090811
000002
Baggins
Frodo
20090801
000003
Gamgee
Samwise
20090820
000004
Cotton
Rosie
20090821
Enjoy!
In my prior post, I covered how to read a comma delimited file into an in-memory DataTable. You could then work with the DataTable as desired to access the fields from the file. You could even bind the resulting DataTable to a grid or other control.
But sometimes you just need to process the file line-by-line and need a simpler solution. That is where the TextFieldParser class comes in.
The TextFieldParser works with any file extension and with any character set, so you can use it with UTF-8 or ANSI text files.
For this example, the text file is as follows:
1, Baggins, Bilbo, 20090811
2, Baggins, Frodo, 20090801
3, Gamgee, Samwise, 20090820
4, Cotton, Rosie, 20090821
NOTE: Be sure to set a reference to Microsoft.VisualBasic.FileIO
Since this class is part of the Visual Basic namespace, only the VB code is presented here. However, you can import this namespace in a C# program and use it.
Dim fileName As String = "testCSV.txt"
Dim dirName As String = _
Path.GetDirectoryName(Application.ExecutablePath)
Using tf As New TextFieldParser _
(Path.Combine(dirName, fileName))
tf.TextFieldType = FileIO.FieldType.Delimited
tf.SetDelimiters(",")
Dim row As String()
While Not tf.EndOfData
Try
row = tf.ReadFields()
For Each field As String In row
' Do whatever to the set of fields
Debug.WriteLine(field)
Next
Catch ex As MalformedLineException
MessageBox.Show("Line " & ex.Message & _
"is not valid and will be skipped.")
End Try
End While
End Using
This code first declares variables to hold the text file name and the directory name. The file can reside in any directory that the user can access. In this example, the file resides in the same directory where the application is executed. But this is not a requirement.
The first using statement in the example code creates an instance of the TextFieldParser class. The parameter to the constructor defines the directory and filename of the text file. The Path.Combine method is used to ensure that the correct slashes are added to the end of the directory name as required.
The code then sets two additional properties. The first is TextFieldType. Since this is a delimited file, the Delimited type is specified. The second is SetDelimiters. This defines the delimiter used for the file. This example uses a comma separated value (CSV) file, so a comma is specified.
The While loop processes each line of the file. The ReadFields method reads each row of the file into a string array. If there is a problem reading the line, the Try/Catch block catches the error, displays a message and continues. (However, in a real application you would want to log this information instead of displaying it to the user.)
The For/Next statement loops through the fields in the row and displays them. This is where you would add the code that processes the values from the text file.
Notice that as you go through each loop, you replace the row variable with the field from the next line. So after a row is processed, you cannot access the data from that row again. (If you do need more random access to the data, you can store each row in a list or other structure, or you can read the data into a DataTable using the technique detailed here.)
The resulting information in the Debug window is as follows:
1
Baggins
Bilbo
20090811
2
Baggins
Frodo
20090801
3
Gamgee
Samwise
20090820
4
Cotton
Rosie
20090821
Enjoy!
There may be times that you need to read fixed length files into your application. For example, you obtain output from a legacy system or other application in a fixed length text file format, and you need to read and use that data in your application.
NOTE: For more information on fixed length files, see this link.
.NET provides several techniques for reading text files. This post focuses on how to read a fixed length text file into a DataTable.
You may find it very useful to read your text file into a DataTable, whether or not you plan to use a database. Reading a text file into a DataTable not only saves you a significant amount of string manipulation coding, it also makes it easy to access the imported data from within your application.
For example, you can use binding to bind the resulting DataTable to a grid or other controls. You can use Linq to DataTables like in this example to manipulate the resulting data. All of the features of the DataTable are then available to you.
BIG NOTE: Many developers have ignored this technique because one look at the code and the developer assumed it is somehow associated with a database, it is NOT. This is referring to in-memory DataTable objects.
For this example, the text file appears as follows:
000001 Baggins Bilbo 20090811
000002 Baggins Frodo 20090801
000003 Gamgee Samwise 20090820
000004 Cotton Rosie 20090821
Notice several things about this file:
- The columns are a fixed width.
- There is no header row that provides the column names. You could add column headers here if desired.
The first step in reading the file is to define a schema.ini file that defines the column widths. The file must follow these specifications:
- The file must be called schema.ini.
- The file must exist in the same directory as the text file.
- The file must be in ANSI format. (See the note at the bottom of this post for information on saving a file to ANSI format.)
The contents of the schema.ini file for the example above is shown below:
[testFixed.txt]
ColNameHeader=False
Format=FixedLength
DateTimeFormat=yyyymmdd
Col1=CustomerId Text Width 6
Col2=LastName Text Width 22
Col3=FirstName Text Width 10
Col4=LastUpdateDate DateTime Width 8
The first line of the file is always the name of the associated text file enclosed in square brackets ([ ]).
The next set of lines define basic attributes of the text file:
- ColNameHeader: In this case, there is no column header in the text file, so this property is set to false. The system will assume that the first line of the text file is the header unless you specify otherwise.
- Format: In this case, the format is FixedLength. The system will assume comma delimited unless you specify otherwise.
- DateTimeFormat: If you have a date in your file, you can specify the format here.
The last set of lines defines each column in the text file. The format of these lines are as follows:
Colx=ColumnName ColumnType Width ColumnWidth
See this link for more information on the contents of the schema.ini file.
You can then read the file using the following code.
In C#:
string fileName = "testFixed.txt";
string dirName = Path.GetDirectoryName(Application.ExecutablePath);
DataTable dt;
using (OleDbConnection cn =
new OleDbConnection(@"Provider=Microsoft.Jet.OleDb.4.0;" +
"Data Source=" + dirName + ";" +
"Extended Properties=\"Text;\""))
{
// Open the connection
cn.Open();
// Set up the adapter
using (OleDbDataAdapter adapter =
new OleDbDataAdapter("SELECT * FROM " + fileName, cn))
{
dt = new DataTable("Customer");
adapter.Fill(dt);
}
}
In VB:
Dim fileName As String = "testCSV.txt"
Dim dirName As String = _
Path.GetDirectoryName(Application.ExecutablePath)
Dim dt As DataTable
Using cn As New OleDbConnection("Provider=Microsoft.Jet.OleDb.4.0;" & _
"Data Source=" & dirName & ";" & _
"Extended Properties=""Text;""")
' Open the connection
cn.Open()
' Set up the adapter
Using adapter As New OleDbDataAdapter( _
"SELECT * FROM " & fileName, cn)
dt = New DataTable("Customer")
adapter.Fill(dt)
End Using
End Using
This code starts by declaring variables to hold the text file name, directory containing the file and the resulting DataTable.
This technique only works with a standard set of file name extensions (see the NOTE at the end of this post). The file can reside in any directory. In this example, the file resides in the same directory where the application is executed. But this is not a requirement.
The first using statement in the example code sets up the connection string for connecting to the directory. It sets the Provider property to use the Microsoft.Jet.OleDb provider. The Data Source property defines the directory containing the text file. The Extended Properties define that the file will be Text ("Text"). The Extended Properties must be within quotes, so double-quotes (VB) or slash quote (C#) are used to escape the quotes.
If a schema.ini file exists in the directory defined as the data source and has a bracketed entry with the text file name, that .ini file is used to determine any other extended properties. So no other extended properties are defined in the connection string itself.
The code then opens the connection, thereby opening the file and the associated schema.ini file. Since this code is in a using statement, the files are automatically closed at the end of the using block.
The second using statement sets of the DataAdapter by defining a Select statement and the open connection. The Select statement selects all of the information from a specific file as defined by the fileName variable.
The code then creates the DataTable, giving the table a name. In this example, the table name is "Customer".
Finally, it uses the Fill method of the TableAdapter to read the data from the text file into the DataTable.
Using the technique detailed here, you can view the resulting DataTable. The column headings were defined by the header in the text file. If you don't have a header, the columns will be giving a default name.
Note how the date in the above screen shot appears as a standard date column.
You can then access the data in the table as you access any other DataTable. For example:
In C#:
foreach (DataRow dr in dt.Rows)
{
Debug.Print("{0}: {1}, {2} LastUpdated: {3}",
dr["CustomerId"],
dr["LastName"],
dr["FirstName"],
dr["LastUpdateDate"]);
}
In VB:
For Each dr As DataRow In dt.Rows
Debug.Print("{0}: {1}, {2} LastUpdated: {3}", _
dr("CustomerId"), _
dr("LastName"), _
dr("FirstName"), _
dr("LastUpdateDate"))
Next
NOTE:
By default, this technique only works with .txt, .csv, .tab, and .asc file extensions. If your file name has a different extension, you can either change the extension in your code before reading the file, or you can update the Extensions key in following registry setting:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text
NOTE:
By default, this technique assumes you are working with ANSI text files. If that is not the case, you can update the CharacterSet key in the same registry setting:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text
Though this is not recommended.
VERY IMPORTANT NOTE:
If you test this sample code by creating a text file with Visual Studio, the resulting text file will be in UTF-8 format. You need to save the file into ANSI format. The easiest way I found to do this is detailed below.
Adding a Text File to your Project:
- Right-click on your project in Visual Studio.
- Select Add | New Item from the context menu.
- Pick Text File from the available templates and click Add.
- Type in the data for the test file or paste in the text from the example at the top of this post.
- Save the file within Visual Studio. This creates a UTF-8 formatted file.
- If you plan to use the directory of the executing application, set the Copy to Output Directory to Copy always in the properties window for the file.
Converting the resulting UTF-8 file to ANSI format:
- Right-click on the file and select Open With …
- Select Notepad.
- Select File | Save As.
- Set the Encoding to ANSI and click Save.
Enjoy!
There may be times that you need to read comma separated value (CSV) files into your application. For example, you obtain output from a legacy system or other application in a comma delimited text file format, and you need to read and use that data in your application.
NOTE: For more information on delimited files, see this link.
.NET provides several techniques for reading text files. This post focuses on how to read a comma delimited text file into a DataTable.
You may find it very useful to read your text file into a DataTable, whether or not you plan to use a database. Reading a text file into a DataTable not only saves you a significant amount of string manipulation coding, it also makes it easy to access the imported data from within your application.
For example, you can use binding to bind the resulting DataTable to a grid or other controls. You can use Linq to DataTables like in this example to manipulate the resulting data. All of the features of the DataTable are then available to you.
BIG NOTE: Many developers have ignored this technique because one look at the code and the developer assumed it is somehow associated with a database, it is NOT. This is referring to in-memory DataTable objects.
For this example, the text file appears as follows:
CustomerId, LastName, FirstName, LastUpdateDate
1, Baggins, Bilbo, 20090811
2, Baggins, Frodo, 20090801
3, Gamgee, Samwise, 20090820
4, Cotton, Rosie, 20090821
Notice several things about this file:
- It is a comma separated value (CSV) file.
- The first line provides the column names. This is optional.
You can read this text file into a DataTable using OleDb as follows.
In C#:
string fileName = "testCSV.txt";
string dirName = Path.GetDirectoryName(Application.ExecutablePath);
DataTable dt;
using (OleDbConnection cn =
new OleDbConnection(@"Provider=Microsoft.Jet.OleDb.4.0;" +
"Data Source=" + dirName + ";" +
"Extended Properties=\"Text;HDR=Yes;FMT=Delimited\""))
{
// Open the connection
cn.Open();
// Set up the adapter
using (OleDbDataAdapter adapter =
new OleDbDataAdapter("SELECT * FROM " + fileName, cn))
{
dt = new DataTable("Customer");
adapter.Fill(dt);
}
}
In VB:
Dim fileName As String = "testCSV.txt"
Dim dirName As String = _
Path.GetDirectoryName(Application.ExecutablePath)
Dim dt As DataTable
Using cn As New OleDbConnection("Provider=Microsoft.Jet.OleDb.4.0;" & _
"Data Source=" & dirName & ";" & _
"Extended Properties=""Text;HDR=Yes;FMT=Delimited""")
' Open the connection
cn.Open()
' Set up the adapter
Using adapter As New OleDbDataAdapter( _
"SELECT * FROM " & fileName, cn)
dt = New DataTable("Customer")
adapter.Fill(dt)
End Using
End Using
This code starts by declaring variables to hold the text file name, directory containing the file and the resulting DataTable.
This technique only works with a standard set of file name extensions (see the NOTE at the end of this post). The file can reside in any directory. In this example, the file resides in the same directory where the application is executed. But this is not a requirement.
The first using statement in the example code sets up the connection to the directory. It sets the Provider property to use the Microsoft.Jet.OleDb provider. The Data Source property defines the directory containing the text file. The Extended Properties define that the file will be Text ("Text"), it has a header (HDR=Yes), and it is in a delimited file format (FRM=Delimited). The Extended Properties must be within quotes, so double-quotes (VB) or slash quote (C#) are used to escape the included quotes.
The code then opens the connection, thereby opening the file. Since this code is in a using statement, the file is automatically closed at the end of the using block.
The second using statement sets of the DataAdapter by defining a Select statement and the open connection. The Select statement selects all of the information from a specific file as defined by the fileName variable.
The code then creates the DataTable, giving the table a name. In this example, the table name is "Customer".
Finally, it uses the Fill method of the TableAdapter to read the data from the text file into the DataTable.
Using the technique detailed here, you can view the resulting DataTable. The column headings were defined by the header in the text file. If you don't have a header, the columns will be giving a default name.
You can then access the data in the table as you access any other DataTable. For example:
In C#:
foreach (DataRow dr in dt.Rows)
{
Debug.Print("{0}: {1}, {2} LastUpdated: {3}",
dr["CustomerId"],
dr["LastName"],
dr["FirstName"],
dr["LastUpdateDate"]);
}
In VB:
For Each dr As DataRow In dt.Rows
Debug.Print("{0}: {1}, {2} LastUpdated: {3}", _
dr("CustomerId"), _
dr("LastName"), _
dr("FirstName"), _
dr("LastUpdateDate"))
Next
NOTE:
By default, this technique only works with .txt, .csv, .tab, and .asc file extensions. If your file name has a different extension, you can either change the extension in your code before reading the file, or you can update the Extensions key in following registry setting:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text
NOTE:
By default, this technique assumes you are working with ANSI text files. If that is not the case, you can update the CharacterSet key in the same registry setting:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text
Though this is not recommended.
VERY IMPORTANT NOTE:
If you test this sample code by creating a text file with Visual Studio, the resulting text file will be in UTF-8 format. You need to save the file into ANSI format. The easiest way I found to do this is detailed below.
Adding a Text File to your Project:
- Right-click on your project in Visual Studio.
- Select Add | New Item from the context menu.
- Pick Text File from the available templates and click Add.
- Type in the data for the test file or paste in the text from the example at the top of this post.
- Save the file within Visual Studio. This creates a UTF-8 formatted file.
- If you plan to use the directory of the executing application, set the Copy to Output Directory to Copy always in the properties window for the file.
Converting the resulting UTF-8 file to ANSI format:
- Right-click on the file and select Open With …
- Select Notepad.
- Select File | Save As.
- Set the Encoding to ANSI and click Save.
Enjoy!
There are many different types of text files that you may need to process in your applications. Some of the more common types are described in this post.
Delimited files
Delimited files separate the fields of the file with some type of a delimiter. The most common delimited files are comma separated value (CSV) files, as shown below.
1, Baggins, Bilbo, 20090811
2, Baggins, Frodo, 20090801
3, Gamgee, Samwise, 20090820
4, Cotton, Rosie, 20090821
Other field delimiters, such as tabs or semicolons, can also be used.
In most cases, delimited files define the end of each record with a CrLf (carriage return/linefeed). So the example above contains four records, one for each line of data.
If you need to read the data from a delimited file into your application, there are features in .NET to assist you.
You can read the file directly using the file classes within .NET and then process the columns using String functions.
Alternatively, you can read the file into a DataTable, even if you have no plans to use this data in a database. This allows you to work with the data as a set of rows and columns instead of using a large number of string manipulation functions. See this link for an example of reading a CSV text file into an in-memory DataTable.
Another option is to read the file using VB's TextFieldParser class. See this link for an example.
Fixed length files
Fixed length files define data within specific columns. In the example below, the customer Id is left-justified in the first 8 columns, the last name is left-justified in the next 20 columns, the first name is left-justified in the next 10 columns, and the last edit date is left justified in the last 10 columns.
000001 Baggins Bilbo 20090811
000002 Baggins Frodo 20090801
000003 Gamgee Samwise 20090820
000004 Cotton Rosie 20090821
As with delimited files, the end of each record is normally defined with a CrLf (carriage return/linefeed). So the example above contains four records, one for each line of data.
This style of text file is often used by legacy systems, especially mainframe systems.
The StringBuilder class in the .NET framework provides easy to use formatting features that help you write code to output fixed length files. See this link for an example.
If you need to read the data from a delimited file into your application, there are features in .NET to assist you.
You can read the file directly using the file classes within .NET and then process the columns using String functions.
Alternatively, you can read the file into a DataTable, even if you have no plans to use this data in a database. This allows you to work with the data as a set of rows and columns instead of using a large number of string manipulation functions. See this link for an example of reading a fixed length file into an in-memory DataTable.
Another option is to read the file using VB's TextFieldParser class. See this link for an example.
XML files
XML (eXtensible Markup Language) files follow the XML specification to define hierarchical data. You use XML elements, defined with start (< >) and end (</ >) tags and XML attributes, define with name/value pairs within a start element tag, to define the structure and content of a file.
For example, this XML file defines a set of customers, each delimited within customer tags. Each field defined for the customer is defined in an element within the customer open and close tags. In the example below, each customer has a CustomerId, LastName, FirstName, and LastUpdateData.
<customers>
<customer>
<CustomerId>1</CustomerId>
<LastName>Baggins</LastName>
<FirstName>Billbo</FirstName>
<LastUpdateDate>20090811</LastUpdateDate>
</customer>
<customer>
<CustomerId>2</CustomerId>
<LastName>Baggins</LastName>
<FirstName>Frodo</FirstName>
<LastUpdateDate>20090801</LastUpdateDate>
</customer>
<customer>
<CustomerId>3</CustomerId>
<LastName>Gamgee</LastName>
<FirstName>Samwise</FirstName>
<LastUpdateDate>20090820</LastUpdateDate>
</customer>
<customer>
<CustomerId>4</CustomerId>
<LastName>Cotton</LastName>
<FirstName>Rosie</FirstName>
<LastUpdateDate>20090821</LastUpdateDate>
</customer>
</customers>
If you have any choice on the type of text file to use for your application, an XML file is the best choice. XML has become the standard for defining data structures due to its simplicity, clarity, self-descriptiveness, and consistency. There are great tools for reading and writing XML files available in the .NET framework. Visual Basic has additional features, called XML literals, that make working with XML files a breeze. See this link for other posting on working with XML files.
Sometimes, however, you have no choice. You have to read a text file that is not in an XML structure. For example, you may need to read files generated by legacy systems or by other software. In that case, you need to use delimited or fixed length files.
This post provides an implementation of a method that saves data to a SQL Server database using a SQL statement. Your application can use this method to insert, update, or delete rows in any database table.
See this for a post detailing how to retrieve data using a SQL statement.
The code is first shown in both VB and C# and then described in detail below.
NOTE: Be sure to set a reference to System.Data.SqlClient.
In C#:
public static class Dac
{
public static int ExecuteNonQuery(string sqlStatement,
params SqlParameter[] arrParam)
{
int retVal=0;
SqlParameter firstOutputParameter = null;
// Open the connection
using (SqlConnection cnn = new SqlConnection(
"Data Source=.\sqlexpress;Initial Catalog=AcmeRentals;
Integrated Security=True"))
{
cnn.Open();
// Define the command
using (SqlCommand cmd= new SqlCommand())
{
cmd.Connection = cnn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlStatement;
// Handle the parameters
if (arrParam != null)
{
foreach (SqlParameter param in arrParam)
{
cmd.Parameters.Add(param);
if (firstOutputParameter == null &&
param.Direction==ParameterDirection.Output &&
param.SqlDbType == SqlDbType.Int)
firstOutputParameter = param;
}
}
// Execute the stored procedure
cmd.ExecuteNonQuery();
// Return the first output parameter value
if (firstOutputParameter != null)
retVal = (int)firstOutputParameter.Value;
}
}
return retVal;
}
}
In VB:
Public Class Dac
Public Shared Function ExecuteNonQuery( _
ByVal sqlStatement As String, _
ByVal ParamArray arrParam() As SqlParameter) As Integer
Dim retVal As Integer = 0
Dim firstOutputParameter As SqlParameter = Nothing
' Open the connection
Using cnn As New SqlConnection(
"Data Source=.\sqlexpress;Initial Catalog=AcmeRentals;
Integrated Security=True")
cnn.Open()
' Define the command
Using cmd As New SqlCommand
cmd.Connection = cnn
cmd.CommandType = CommandType.Text
cmd.CommandText = sqlStatement
' Handle the parameters
If arrParam IsNot Nothing Then
For Each param As SqlParameter In arrParam
cmd.Parameters.Add(param)
If firstOutputParameter Is Nothing AndAlso _
param.Direction = _
ParameterDirection.Output AndAlso _
param.SqlDbType = SqlDbType.Int Then
firstOutputParameter = param
End If
next
End If
' Execute the Query
cmd.ExecuteNonQuery()
' Return the first output parameter value
If firstOutputParameter IsNot Nothing Then
retVal = CInt(firstOutputParameter.Value)
End If
End Using
End Using
Return retVal
End Function
End Class
The ExecuteNonQuery method is in my Dac (data access component) class and is defined public and static (shared in VB). It is public so it can be called from any other code. It is static/shared because it does not use or retain any state. That allows the function to be called without creating an instance of the class containing the function.
The function has two parameters. The first is the SQL statement. Since this code calls the SQLCommand ExecuteNonQuery, it assumes that the SQL statement contains a non-query statement such as an Insert, Update, or Delete. See this link for example stored procedures.
The second parameter is a parameter array that allows defining any parameters required by the stored procedure. Insert and Update stored procedures for a table normally define a parameter for any field in the table that can be updated.
NOTE: A parameter array permits a variable number of arguments to be passed to a method. So this allows any number of parameters.
The function will return an integer value containing the first integer output parameter defined for the stored procedure. This provides a “quick and dirty” way to return an autonumber column defining the Id of a new row. You can change this as appropriate to handle your primary keys.
The first two statements set up for the return value.
The first Using statement defines the connection. You will need to replace the connection string in the example with a connection string appropriate for your database.
The connection is then opened. The connection is automatically closed at the end of the Using block.
The second Using statement defines the command. In this case, the command is a SQL statement and the CommandText property of the command defines the text of the SQL statement.
If parameters were passed in, the parameters are added to the command’s Parameters. As the parameters are processed, the code looks for the first output parameter that is an integer. The code will later return the value of this parameter, if it is found.
The code then calls the ExecuteNonQuery method of the command to execute the query.
Finally, if there was an integer output parameter found, its value is retrieved and returns.
As an example, this is how you call this method to insert a new Customer row.
In C#:
SqlParameter idParameter = new SqlParameter("@CustomerID", CustomerId);
idParameter.Direction = ParameterDirection.Output;
int retVal = Dac.ExecuteNonQuery("Insert into Customer " +
"(LastName, FirstName, LastUpdateDate) Values " +
"(@LastName, @FirstName, GETDATE()) " +
"Select @CustomerID = @@Identity",
idParameter,
new SqlParameter("@LastName", LastName),
new SqlParameter("@FirstName", FirstName));
In VB:
Dim idParameter As SqlParameter = _
New SqlParameter("@CustomerId", CustomerId)
idParameter.Direction = ParameterDirection.Output
Dim retVal As Integer = Dac.ExecuteNonQuery("Insert into Customer " & _
"(LastName, FirstName, LastUpdateDate) Values " & _
"(@LastName, @FirstName, GETDATE()) " & _
"Select @CustomerID = @@Identity", _
idParameter, _
New SqlParameter("@LastName", LastName), _
New SqlParameter("@FirstName", FirstName)) This code first creates a parameter for the CustomerId. This parameter is defined as an output parameter so the autonumber field set by the database when the row is insert can be returned.
Here is an example of calling this method to update a Customer rows.
In C#:
Dac.ExecuteNonQuery("Update Customer " +
"SET LastName = @LastName, " +
"FirstNAme = @FirstName, " +
"LastUpdateDate = GETDATE()" +
"WHERE CustomerId = @CustomerId",
new SqlParameter("@CustomerID", CustomerId),
new SqlParameter("@LastName", LastName),
new SqlParameter("@FirstName", FirstName)); In VB:
Dac.ExecuteNonQuery("Update Customer " & _
"SET LastName = @LastName, " & _
"FirstNAme = @FirstName, " & _
"LastUpdateDate = GETDATE()" & _
"WHERE CustomerId = @CustomerId", _
New SqlParameter("@CustomerId", CustomerId), _
New SqlParameter("@LastName", LastName), _
New SqlParameter("@FirstName", FirstName)) The update example is much more straightforward because it does not require any return parameters.
Enjoy!
P.S. (Edited 8/21/09)
In the interest of having code that any reader can copy/paste into their own application without much external setup, I have removed the code that uses an application setting to define the connection string.
The removed text is here: "The connection string itself is stored in the configuration file using the Settings feature in Visual Studio. (See this for information on settings in C# and this for information on settings in VB.)"
Instead, a connection string is hard-coded into the example. Though I don't recommend this approach, it will help readers use these examples more easily.
I have an XML string as follows:
<States>
<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>
I now want to add another area under Milwaukee called "Wauwatosa".
The code to accomplish this task is as follows.
In C#:
// Be sure to set a reference to System.Core and System.Xml.Linq
XElement states = XElement.Load("testXML.xml");
// New element under the Milwaukee region
XElement newElement = XElement.Parse(@"<Area name='Wauwatosa'/>");
// Find the desired parent element
// Using LINQ
XElement parentNode;
var parentQuery = from XElement r in states.Descendants("Region")
where r.Attribute("name").Value == "Milwaukee"
select r;
parentNode = parentQuery.FirstOrDefault();
// Using Lambda expression
parentNode = states.Descendants("Region").
Where(r => r.Attribute("name").Value ==
"Milwaukee").FirstOrDefault();
if (parentNode != null)
parentNode.Add(newElement);
states.Save("Revised.xml");
In VB:
' Be sure to set a reference to System.Core and System.Xml.Linq
Dim states As XElement = XElement.Load("testXML.xml")
' New element under the Milwaukee region
Dim newElement As XElement = <Area name="Wauwatosa"/>
' Find the desired parent element
' Using LINQ
Dim parentNode As XElement
Dim parentQuery = From r As XElement In states...<Region> _
Where r.@<name> = "Milwaukee"
parentNode = parentQuery.FirstOrDefault()
' Using Lambda expression
parentNode = states...<Region>.Where(Function(r) r.@<name> = _
"Milwaukee").FirstOrDefault
If (parentNode IsNot Nothing) Then
parentNode.Add(newElement)
End If
states.Save("Revised.xml")
This code first loads in the XML file containing the XML at the top of this post. The code then defines the new element for Wauwatosa using XML literals in VB and the XElement properties and methods in C#.
NOTE: The XElement Descendants property works in VB as well.
The next set of code can be done using LINQ or using Lambda expressions. Use either one, but not both. :-)
If the appropriate parent element was found, the new element is added to it. The Add method adds the element as a child element.
Finally, the code saves the revised XML:
<States>
<State name="Wisconsin">
<Regions>
<Region name="Milwaukee">
<Area name="Mukwanago" />
<Area name="Germantown" />
<Area name="Wauwatosa" />
</Region>
<Region name="Fox Valley">
<Area name="Oshkosh" />
<Area name="Appleton" />
</Region>
</Regions>
</State>
</States>
Enjoy!
Another use of anonymous types is for processing directories or files. For example, your application needs to collect a set of files and then process them based on a subset of the file properties.
[To begin with an overview of anonymous types, start here.]
Here is an example.
In C#:
// Query the set of files
var fileTemplateQuery = from FileInfo f in
new DirectoryInfo(@"C:\temp")
.GetFiles("*.*", SearchOption.AllDirectories)
where f.Extension.ToLower() == ".xml" ||
f.Extension.ToLower() == ".txt"
select new
{
DateLastModified = f.LastWriteTime,
Extension = f.Extension,
Size = f.Length,
FileName = f.Name
};
foreach (var f in fileTemplateQuery.OrderBy(file =>
file.DateLastModified))
// Do whatever you need to with the files
Debug.WriteLine(f.FileName);
In VB:
Dim fileTemplateQuery = From f As FileInfo In _
My.Computer.FileSystem.GetDirectoryInfo("C:\temp") _
.GetFiles("*.*", SearchOption.AllDirectories) _
Where f.Extension.ToLower = ".xml" OrElse _
f.Extension.ToLower = ".txt" _
Select New With _
{ _
.DateLastModified = f.LastWriteTime, _
.Extension = f.Extension, _
.Size = f.Length, _
.FileName = f.Name _
}
For Each f In _
fileTemplateQuery.OrderBy(Function(file) file.DateLastModified)
' Do whatever you need to with the files
Debug.WriteLine(f.FileName)
Next
This code uses Linq to find a specific set of files. In this case, it finds all of the files in C:\temp and its subdirectories where the extension is .xml or .txt. It then uses an anonymous type to retain the set of desired file properties.
The for/each statement loops through the set of anonymous types in order by date and performs whatever operation is required to process the files.
Enjoy!
The last several posts have provided examples of using anonymous types. This post backs up a little and provides an introduction if you are not already familiar with anonymous types.
If you are familiar with anonymous types and are looking for more examples, check out these links:
Anonymous types are new in C# 3.0 and VB 9.0 (Visual Studio 2008). An anonymous type allows you to encapsulate a set of properties (not methods) into a specific object without explicitly defining a type.
When you create a class, such as a Customer class, you are explicitly creating a Customer type with defined properties and methods. The resulting class defines a Customer type which is a named type with the name Customer.
There are times, however, where you need a temporary type. You could explicitly create this type using a class, but that seems like overkill if you only plan to use the type within one routine. This is where an anonymous type is very useful.
It is important to note that the properties of an anonymous type are read/write in VB, but read-only in C#.
You can create an anonymous type manually using the following syntax.
In C#:
var stats = new {Name = String.Empty, Max = 0,
Min = 0, Ave = 0};
In VB:
Dim stats = New With {.Name = String.Empty, .Max = 0, _
.Min = 0, .Ave = 0}
This code creates an anonymous type with Name, Max, Min, and Ave properties. This anonymous type defines some statistics. For example, a list of students each have a set of scores for a class. This anonymous type provides the student's name and score statistics.
However, using the syntax in this example is not the usual way to create anonymous types. Rather, the most common technique for creating anonymous types is within a Linq statement.
In C#:
var scoreQuery = from Student s in Students
select new {Name = s.LastName,
Max = s.Scores.Max(),
Min = s.Scores.Min(),
Ave = s.Scores.Average()};
foreach (var s in scoreQuery)
{
Debug.Print("Student Name: {0}", s.Name);
Debug.Print("Min: {0}, Max: {1}, Ave: {2}", s.Max, s.Min, s.Ave);
}
In VB:
Dim scoreQuery = From s As Student In Students _
Select New With {.Name = s.LastName, _
.Max = s.Scores.Max(), _
.Min = s.Scores.Min(), _
.Ave = s.Scores.Average()}
For Each s In scoreQuery
Debug.Print("Student Name: {0}", s.Name)
Debug.Print("Min: {0}, Max: {1}, Ave: {2}", s.Max, s.Min, s.Ave)
Next
NOTE: This code assumes you have a Student class with LastName and Scores properties (see below) and a populated List<Student> with several students and their scores.
The Linq statement in this example processes each student in the list of students. For each student, it creates an instance of the anonymous type with the student's name and the appropriate statistics.
The for/each loops through the resulting set of anonymous types and displays the results.
In my example, the results are as follows:
Student Name: Baggins
Min: 97, Max: 65, Ave: 87.2
Student Name: Cotton
Min: 100, Max: 90, Ave: 93.6
Student Name: Brandybuck
Min: 88, Max: 65, Ave: 79.6
Here is the minimum code for the Student class used in this example:
In C#:
public class Student
{
public string LastName { get; set; }
public List<int> Scores { get; set; }
}
In VB:
Public Class Student
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
_LastName = value
End Set
End Property
Private _Scores As List(Of Integer)
Public Property Scores() As List(Of Integer)
Get
Return _Scores
End Get
Set(ByVal value As List(Of Integer))
_Scores = value
End Set
End Property
End Class
Anyone else found interesting ways to use anonymous types? I'd enjoy hearing about it…
Enjoy!
Similar to my prior post here that details how to use anonymous types to display word counts, this post details how to track information on the physical lines that contain each word. Again, this specific task is more like a homework assignment than a business issue, but it does demonstrate how to work with lists of anonymous types.
[To begin with an overview of anonymous types, start here.]
First, the code needs an extension method. This extension method returns a generic List of a particular anonymous type.
In C#:
public static List<T> ListOfType<T>(this T type)
{
return new List<T>();
}
In VB:
NOTE: This code must reside in a module.
<Extension()> _
Public Function ListOfType(Of T)(ByVal type As T) As List(Of T)
Return New List(Of T)
End Function
Without this extension method, you would have no way to create a List<of anonymous type>. You can use this extension method any time you want to work with a list of your anonymous types.
The following code defines a list of all words in the sentence along with each physical line containing the word.
NOTE: Be sure to set a reference to System.Text.RegularExpressions.
In C#:
// Sample string
string sampleText = @"That that is, is.
That that is not, it not.
Is that it? It is.";
// Convert to lower case
sampleText = sampleText.ToLower();
// Split into lines
char[] lineSeparator =new char[1] {Convert.ToChar(10)};
string[] lineArray = sampleText.Split(lineSeparator,
StringSplitOptions.RemoveEmptyEntries);
// Define the anonymous type and List(of anonymous type)
var wordLines = new {Word = String.Empty, Line = 0};
var listOfWordLines = wordLines.ListOfType();
string lineText;
string[] lineWords;
string[] separators = new string[4] {" ", ".", ",", "?"};
for (int i = 0; i < lineArray.Count(); i++)
{
lineText = Regex.Replace(lineArray[i], @"\s+", " ");
lineWords = lineText.Split(separators,
StringSplitOptions.RemoveEmptyEntries);
// Using an anonymous type
int lineNumber = i + 1;
var lineQuery = from string w in lineWords.Distinct()
select new {Word = w, Line = lineNumber};
listOfWordLines.AddRange(lineQuery);
}
var wordGroupQuery = from wordLine in listOfWordLines
group wordLine by wordLine.Word into g
orderby g.Key
select new { word = g.Key, wordGroup = g };
foreach (var g in wordGroupQuery)
{
Debug.Write(g.word + ": ");
foreach (var lineNumber in g.wordGroup)
Debug.Write(lineNumber.Line + " ");
Debug.WriteLine("");
}
In VB:
NOTE: For the VB code, be sure to also set a reference to System.Xml, System.Xml.Linq, and System.Core.
' Sample string
Dim sampleText As String = <string>That that is, is.
That that is not, it not.
Is that it? It is.</string>.Value
' Convert to lower case
sampleText = sampleText.ToLower
' Split into lines
Dim lineSeparator() As String = {Chr(10)}
Dim lineArray() As String = sampleText.Split(lineSeparator, _
StringSplitOptions.RemoveEmptyEntries)
' Define the anonymous type and List(of anonymous type)
Dim wordLines = New With {.Word = String.Empty, .Line = 0}
Dim listOfWordLines = wordLines.ListOfType()
Dim lineText As String
Dim lineWords() As String
Dim separators() As String = {" ", ".", ",", "?"}
For i As Integer = 0 To lineArray.Count - 1
lineText = Regex.Replace(lineArray(i), "\s+", " ")
lineWords = lineText.Split(separators, _
StringSplitOptions.RemoveEmptyEntries)
' Using an anonymous type
Dim lineNumber As Integer = i + 1
Dim lineQuery = From w As String In lineWords.Distinct _
Select New With {.Word = w, .Line = lineNumber}
listOfWordLines.AddRange(lineQuery)
Next
Dim wordGroupQuery = From wordLine In listOfWordLines _
Group wordLine By currentWord = wordLine.Word Into Group _
Select word = currentWord, wordGroup = Group _
Order By Word
For Each g In wordGroupQuery
Debug.Write(g.word & ": ")
For Each lineNumber In g.wordGroup
Debug.Write(lineNumber.Line & " ")
Next
Debug.WriteLine("")
Next
This code first builds a sample string. The C# code uses a verbatim string literal (@) to ensure that the string is interpreted verbatim. In VB, the code uses the XML literals feature new in .Net 3.5 to build a sample string.
The code converts the string to lower case so that the routine treats “The” and “the” as the same word. It then splits the string into its physical lines. The lineArray contains the text of each physical line in the string.
The next two lines of code define the anonymous type and the List of the anonymous type. You can define any desired properties of an anonymous type by adding them within the { }, separated by commas. In this example, two properties are defined: Word and Line. The Word property is the word from the string. The Line property is the number of the line containing the word. The ListOfType extension method creates a generic List of this type.
The code then loops through the lineArray. It first removes excess spaces from the line’s text, and any other white-space characters using a Regular Expression. It then splits the line of text into an array of words. If your string includes other punctuation marks, you will need to add them to the separators array.
The code uses LINQ to find the unique set of words. The Distinct method ensures that only unique words in the line are processed.
The select new syntax defines an anonymous type that is the same as the anonymous type defined earlier. This type defines the the set of words and line numbers for each line in the lineArray. The AddRange method of the List is used to append the words for each line into a single list.
At this point, the list of words and their physical line numbers is complete. The remaining code organizes the list for display. The code uses a Group By query to group the list by word. The final for/each loop then uses the groups to first display the word, then the list of line numbers that contain the word.
The result is:
is: 1 2 3
it: 2 3
not: 2
that: 1 2 3
Enjoy!
This may be more of a homework assignment for a programming class than something you would do in your applications, but it is a good example of using anonymous types, which are new in .Net 3.5 in both VB and C#.
[To begin with an overview of anonymous types, start here.]
NOTE: Be sure to set a reference to System.Text.RegularExpressions.
In C#:
// Sample string
string sampleText = @"That that is, is.
That that is not, it not.
Is that it? It is.";
// Convert to lower case and convert double-spaces to a single space
sampleText = sampleText.ToLower();
sampleText = Regex.Replace(sampleText, @"\s+", " ");
string[] separators = new string[4] {" ", ".", ",", "?"};
string[] wordArray = sampleText.Split(separators,
StringSplitOptions.RemoveEmptyEntries);
// Sort the result
Array.Sort(wordArray);
// Using an anonymous type
var query = from string w in wordArray.Distinct()
select new {Word = w,
Count = wordArray.Count(wordToCount => wordToCount == w)};
foreach (var item in query)
Debug.WriteLine(item.Word + ": " + item.Count);
In VB:
NOTE: For the VB code, be sure to also set a reference to System.Xml, System.Xml.Linq, and System.Core.
' Sample string
Dim sampleText As String = <string>That that is, is.
That that is not, it not.
Is that it? It is.</string>.Value
' Convert to lower case and convert double-spaces to a single space
sampleText = sampleText.ToLower
sampleText = Regex.Replace(sampleText, "\s+", " ")
Dim separators() As String = {" ", ".", ",", "?"}
Dim wordArray() As String = sampleText.Split(separators, _
StringSplitOptions.RemoveEmptyEntries)
' Sort the result
Array.Sort(wordArray)
' Using an anonymous type
Dim query = From w As String In wordArray.Distinct _
Select New With {.Word = w, _
.Count = wordArray.Count(Function(wordToCount) wordToCount = w)}
For Each item In query
Debug.WriteLine(item.Word & ": " & item.Count)
Next
This code first builds a sample string. (Anyone recognize what movie this string came from?)
The C# code uses a verbatim string literal (@) to ensure that the string is interpreted verbatim. In VB, the code uses the XML literals feature new in .Net 3.5 to build a sample string.
The code converts the string to lower case so that the word count counts “The” and “the” as the same word. It then removes excess spaces, linefeeds, and other white-space characters.
It uses the string Split method to convert the string to an array of words and then sorts the words. If your string includes other punctuation marks, you will need to add them to the separators array.
The code uses LINQ to find the unique set of words and their counts. The District method is used to process only unique words from the list of words. This prevents duplicate words in the list.
The select new syntax defines an anonymous type to build a type comprised of the word itself and its count. You can define any desired properties of an anonymous type by adding them within the { }, separated by commas. In this example, two properties are defined: Word and Count. The Word property is the unique word. The Count property is the count of those words within the list. The Count property uses a Lambda expression to count the words.
Each item of the anonymous type is then displayed to the Debug window as follows:
is: 5
it: 3
not: 2
that: 5
This lists the word and the number of times it occurs in the string.
Enjoy!
P.S. (Edited 8/19/09) Though it does not demonstrate anonymous types, Eric Smith provided a *very* concise technique for counting words in a string using regular expressions and lambda expressions (see Comments below). I updated the code slightly to include the OrderBy and I provided the VB version of the code:
In C#:
foreach (var g in Regex.Matches(sampleText.ToLower(), @"\w+")
.Cast<Match>()
.GroupBy(m => m.Value)
.OrderBy(m => m.Key))
Debug.WriteLine(g.Key + ": " + g.Count());
In VB:
For Each g In Regex.Matches(sampleText.ToLower(), "\w+") _
.Cast(Of Match)() _
.GroupBy(Function(m) m.Value) _
.OrderBy(Function(m) m.Key)
Debug.WriteLine(g.Key & ": " & g.Count())
Next
Some of the collections in the Microsoft Office object models implement IEnumerable. The IEnumerable interface provides the ability to perform a for/each against the collection. With .NET 3.5, a Cast extension method of IEnumerable allows you to work with these collections using Linq.
Microsoft Word
For example, say you want to bind the set of open Word document names in a ComboBox.
First, set a reference to the desired version of the Microsoft Word Object Library from the COM tab of the Add Reference dialog. The resulting reference appears as Microsoft.Office.Interop.Word.
In C#:
// Add to the top of the code file
using Word = Microsoft.Office.Interop.Word;
// Add to a subroutine
Word.Application Wd;
Word.Document doc;
Word.Document doc2;
object missingValue = Missing.Value;
// Start Word and get Application object
Wd = new Word.Application();
// Define documents
doc = Wd.Documents.Add(ref missingValue,ref missingValue,
ref missingValue,ref missingValue );
doc2 = Wd.Documents.Add(ref missingValue, ref missingValue,
ref missingValue, ref missingValue);
// Use Linq to access the document names.
var query = from d in Wd.Documents.Cast<Word.Document>()
select d.Name;
comboBox1.DataSource = query.ToList();
// Or use Lambda expressions
var query2 = Wd.Documents.Cast<Word.Document>().Select(d=> d.Name);
comboBox1.DataSource = query2.ToList();
// Close
doc.Close(ref missingValue, ref missingValue, ref missingValue);
doc = null;
doc2.Close(ref missingValue, ref missingValue, ref missingValue);
doc2 = null;
Wd.Quit(ref missingValue, ref missingValue, ref missingValue);
// Clean up
// NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect() ;
In VB:
' Add to the top of the code file
Imports Word = Microsoft.Office.Interop.Word
' Add to a subroutine
Dim Wd As Word.Application
Dim doc As Word.Document
Dim doc2 As Word.Document
' Start Word and get Application object
Wd = New Word.Application
' Define documents
doc = Wd.Documents.Add
doc2 = Wd.Documents.Add
' Use Linq to access the document names.
Dim query = From d In Wd.Documents.Cast(Of Word.Document)() _
Select d.Name
ComboBox1.DataSource = query.ToList
' Or use Lambda expressions
Dim query2 = Wd.Documents.Cast(Of Word.Document) _
.Select(Function(d) d.Name)
ComboBox1.DataSource = query2.ToList
' Close
doc.Close()
doc = Nothing
doc2.Close()
doc2 = Nothing
Wd.Quit()
' Clean up
' NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
In both of these examples, the code starts Word, creates two Word documents, uses either Linq or a Lambda expression to define a query and then binds the resulting set of document names to a Combo Box.
Notice the missingValue variable in the C# code that is not in the VB code. VB supports default parameters, but C# does not. So any time a parameter is defined for a Word method, C# must provide it. VB will use the default parameter values.
NOTE: A new feature in C# 4.0 (Visual Studio 2010) allows for default parameters in C# as well, dramatically simplifying the C# code that interacts with Word or Excel.
As another example, the following code retrieves all of the words from the defined Word document.
In C#:
var query = from w in doc.Words.Cast<Word.Range>()
select w.Text;
comboBox1.DataSource = query.ToList(); In VB:
Dim query = From w In doc.Words.Cast(Of Word.Range)() _
Select w.Text
ComboBox1.DataSource = query3.ToList
This code retrieves all of the words in the document defined by the doc variable. Instead of selecting the list of words, you could use any Linq feature such as finding only a specific set of words that match a criteria or counting the number of occurrences of a given word.
Microsoft Excel
This technique works with Excel as well. Say you want to bind the list of spreadsheets in an Excel workbook.
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.
In C#:
// Add to the top of the code file
using Excel = Microsoft.Office.Interop.Excel;
// Add to a subroutine
Excel.Application oXL;
Excel.Workbook oWB;
Excel.Worksheet oSheet;
// Start Excel and get Application object.
oXL = new Excel.Application();
// Get a new workbook.
oWB = oXL.Workbooks.Add(Missing.Value);
// Get the active sheet and change its name
oSheet = (Excel.Worksheet)oWB.ActiveSheet ;
oSheet.Name = "Test";
// Use Linq to access the spreadsheet names.
var query = from s in oXL.Worksheets.Cast<Excel.Worksheet>()
select s.Name;
comboBox1.DataSource = query.ToList();
// Or use Lambda expressions.
var query2 = oXL.Worksheets.Cast<Excel.Worksheet>()
.select(s => s.Name);
comboBox1.DataSource = query2.ToList(); // Close
oSheet = null;
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:
' Add to the top of the code file
Imports Excel = Microsoft.Office.Interop.Excel
' Add to a subroutine
Dim oXL As Excel.Application
Dim oWB As Excel.Workbook
Dim oSheet As Excel.Worksheet
' Start Excel and get Application object.
oXL = New Excel.Application
' Get a new workbook.
oWB = oXL.Workbooks.Add
' Get the active sheet and change its name
oSheet = DirectCast(oWB.ActiveSheet, Excel.Worksheet)
oSheet.Name = "Test"
' Use Linq to access the spreadsheet names.
Dim query = From s In oXL.Worksheets.Cast(Of Excel.Worksheet)() _
Select s.Name
ComboBox1.DataSource = query.ToList
' Or use Lambda expressions
Dim query2 = oXL.Worksheets.Cast(Of Excel.Worksheet) _
.Select(Function(s) s.Name)
ComboBox1.DataSource = query2.ToList
' Close
oSheet = Nothing
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()
In both examples, the code starts Excel, changes the name of the active sheet, uses either Linq or a Lambda expression to define a query and then binds the resulting set of sheet names to a Combo Box.
Enjoy!
EDITED 11/16/09: Added information on setting the appropriate reference.
More Posts
Next page »