Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

Archive for LINQ

August 20, 2009

Adding Nodes to an XML String

Filed under: C#,Lambda Expressions,LINQ,VB.NET,XML @ 5:49 pm

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!

August 19, 2009

Processing Files Using Anonymous Types

Filed under: C#,LINQ,VB.NET @ 4:06 pm

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!

Anonymous Types: An Introduction

Filed under: C#,Lambda Expressions,LINQ,VB.NET @ 2:31 pm

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!

August 18, 2009

Defining Lists of Anonymous Types

Filed under: C#,LINQ,VB.NET @ 1:44 am

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!

August 17, 2009

Counting Words in a String Using Anonymous Types

Filed under: C#,Lambda Expressions,LINQ,VB.NET @ 1:10 pm

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

August 14, 2009

Using Linq with Microsoft Word and Excel

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.

July 28, 2009

Inferred Typing

Filed under: C#,LINQ,VB.NET @ 2:01 pm

If you are using Visual Studio 2008 (.NET Framework 3.5) or newer, you can leverage inferred typing. Inferred typing allows the compiler to infer your data types instead of you having to explicitly define the type. Variables that use inferred typing are still strongly typed, they just depend on .NET for determining (or inferring) the type.

Inferred typing is sometimes referred to as “duck typing”. This is from the truth that if something looks like a duck and quacks like a duck, it must be a duck. For more general information on inferred typing (or type inference), see this.

The compiler infers your data type based on its context. Here are some examples.

In C#:

var i = 1;

var x = "Hello";

var arr = new int[] {1, 2, 3, 4};

var c = new Customer();

var y = null; // Generates an error

To use inferred typing in C#, use the var keyword as in the above examples. Note that var is not short for variant and does not imply that the variables are weakly typed. Rather, the var keyword defines that the compiler should infer the type based on the context.

In VB:

Dim i = 1

Dim x = "Hello"

Dim arr() = New Integer() {1, 2, 3, 4}

Dim c = new Customer()

Dim y = Nothing

To use inferred typing in VB, you need to set Option Infer On:

  1. Double-Click on My Project under Solution Explorer.
  2. Click the Compile tab.
  3. Set Option Infer On.

If Option Infer is On, simply Dim the variable to take advantage of inferred typing. This does not imply that the variables are weakly typed or are of type Object. Rather, using Dim without a data type when Option Infer is On defines that the compiler should infer the type based on the context.

In the first example under either language, the inferred type of i is integer because it is assigned to 1. In the second example, x is inferred to be of type string. The arr variable is inferred to be of an integer array type.

The fourth example infers the type of c to be Customer and creates a new instance of the Customer class. This is less necessary as a shortcut in VB where you can also do this:

Dim c as New Customer

In the last example, VB infers the type of y to be Object. C# won’t attempt to infer the type and instead generates a compile error.

You can view the type that is inferred by hovering over the value in the Visual Studio text editor. In VB, hover over the variable. In C#, hover over the var keyword.

BUT – is this a good idea? Should you be leaving it up to the compiler to define your types?

The recommended best practice regarding inferred typing is as follows:

  • Do NOT use inferred typing for variables of intrinsic types (like in the above integer and string examples).
  • DO use inferred typing when creating a new instance, such as in the new Customer example.
  • DO use inferred typing when dealing with anonymous types (see this or this).
  • DO use inferred typing when working with LINQ and Lambda expressions.

Though a quick Bing of this topic shows that not all developers agree with these guidelines.

A common use of inferred typing is with LINQ and Lambda expressions because the data types of these expressions are often long and tedious.

For example:

In C#:

List<int> myList = new List<int>() { 1, 6, 8, 3, 2, 4, 15 };

var result = from item in myList
               where item < 5
               select item;

In VB:

Dim myArr() As Integer = {1, 6, 8, 3, 2, 4, 15}
Dim myList As List(Of Integer) = myArr.ToList

Dim result = From item In myList _
                Where item < 5 _
                Select item

NOTE: VB does not support List initializers but it does support array initializers. So to simplify the amount of code, an array initializer was used and then the array was converted to a List.

In both cases, the result variable type is System.Collections.Generic.IEnumerable<int>. Yuck!

Enjoy!

EDIT: 8/26/12

Two things to add to this:

1) VB does now support List initializers as of VB 10 (VS 2010).

2) In practice over the past few years, I have found that I also ignore the first recommendation for inferred typing. Why include the name of the type (string or int) when it is so obvious that you are assigning to a string or an integer?

July 23, 2009

Linq: Sorting a DataTable

Filed under: C#,Data,Lambda Expressions,LINQ,VB.NET @ 6:24 pm

Last night, my husband (who is also a .NET developer) asked me about sorting a DataTable by user-defined columns. (Yes, we are a pretty exciting couple!)

Most developers that work with DataTables know how to sort the DataTable using a DataView.Sort. If you are interested in that technique, you can view the msdn documentation here. This post is about sorting using Linq.

NOTE: Be sure to set a reference to System.Data.DatasetExtensions.

If you know ahead of time which columns of the DataTable should be sorted, you can use LINQ to DataTables like this.

In C#:

var query = from c in dt.AsEnumerable()
            orderby c.Field<DateTime?>("LastPurchaseDate"),
                    c.Field<string>("LastName") descending
            select c;
DataView dv   = query.AsDataView();

In VB:

Dim query = From c In dt.AsEnumerable _
            Order By c.Field(Of DateTime?)("LastPurchaseDate"), _
                     c.Field(Of String)("LastName") Descending
Dim dv As DataView = query.AsDataView

The dt variable represents the table you wish to sort. The AsEnumerable is an extension method on the DataTable that allows you to use Linq with it. The Order By clause takes any number of columns. You must specify the data type and then the column name or index. To sort descending, use the Descending keyword.

A nullable DateTime (represented by DateTime?) ensures that the code correctly handles any null values in the table.

In the above examples, the data is sorted first by LastPurchaseDate (ascending) and then by LastName (descending).

The resulting DataView can be bound to a grid or other control.

If you prefer Lambda expressions, you can do this same sort as follows:

In C#:

var query2 = dt.AsEnumerable()
          
.OrderBy(c=> c.Field<DateTime?>("LastPurchaseDate"))
           .ThenByDescending(c=> c.Field<string>("LastName"));
DataView dv2   = query.AsDataView();

In VB:

Dim query2 = dt.AsEnumerable _
    .OrderBy(Function(c) c.Field(Of DateTime?)("LastPurchaseDate")) _
    .ThenByDescending(Function(c) c.Field(Of String)("LastName"))
Dim dv2 As DataView = query.AsDataView

This code performs the same sort as the prior examples.

But neither of these techniques work well if you want the user to select any number of columns to use for the sort. In that case, you need something a little more full-featured.

The first step to building a more generalized sort is to build your own comparer class.

In C#:

private class RowComparer : IComparer<DataRow>
{
    public Dictionary<int, SortOrder> SortColumns { get; set; }

    public int Compare(System.Data.DataRow x, System.Data.DataRow y)
    {
        int returnValue  = 0;
        foreach (int key in SortColumns.Keys)
        {
            int compareResult ;

            // Check for nulls
            if (x.ItemArray[key] == DBNull.Value
                       && y.ItemArray[key] == DBNull.Value)
                compareResult = 0;
            else if (x.ItemArray[key] == DBNull.Value)
                compareResult = -1;
            else if (y.ItemArray[key] == DBNull.Value)
                compareResult = 1;
            else
            {
                // Execute the compare based on the column type
                if (x.Table.Columns[key].DataType.Name ==
                                            typeof(Decimal).Name)
                    compareResult =
                         Decimal.Compare((decimal)x.ItemArray[key],
                                         (decimal)y.ItemArray[key]);

                else if (x.Table.Columns[key].DataType.Name ==
                                            typeof(DateTime).Name)
                    compareResult =
                         DateTime.Compare((DateTime)x.ItemArray[key],
                                          (DateTime)y.ItemArray[key]);
                else
                    // Compare anything else as a string
                    compareResult =
                         String.Compare(x.ItemArray[key].ToString(),
                                        y.ItemArray[key].ToString());
            }

            if (compareResult != 0)
            {
                returnValue =
                   SortColumns[key] == SortOrder.Ascending ?
                                  compareResult: -compareResult;
                break;
            }
        }
        return returnValue;
    }
}

In VB:

Public Class RowComparer
    Implements IComparer(Of DataRow)

    Private _sortColumns As Dictionary(Of Integer, SortOrder)
    Public Property SortColumns() As Dictionary(Of Integer, SortOrder)
        Get
            Return _sortColumns
        End Get
        Set(ByVal value As Dictionary(Of Integer, SortOrder))
            _sortColumns = value
        End Set
    End Property

    Public Function Compare(ByVal x As System.Data.DataRow, _
         ByVal y As System.Data.DataRow) As Integer _
         Implements System.Collections.Generic.IComparer( _
                            Of System.Data.DataRow).Compare
        Dim returnValue As Integer = 0
        For Each key As Integer In SortColumns.Keys
            Dim compareResult As Integer

            ‘ Handle DBNull.
            Dim xValue As Object = _
              If(x.Item(key) Is DBNull.Value, Nothing, x.Item(key))
            Dim yValue As Object = _
              If(y.Item(key) Is DBNull.Value, Nothing, y.Item(key))

            ‘ Execute the appropriate compare based on the column type
            Select Case x.Table.Columns(key).DataType.Name
                Case GetType(Decimal).Name
                    compareResult = _
                        Decimal.Compare(CType(xValue, Decimal), _
                                      
CType(yValue, Decimal))

                Case GetType(DateTime).Name
                    compareResult = _
                        DateTime.Compare(CType(xValue, DateTime), _
                                         CType(yValue, DateTime))

                Case Else
                    ‘ Compare anything else as a string
                    compareResult = _
                         String.Compare(x.Item(key).ToString, _
                                        y.Item(key).ToString)
            End Select

            If compareResult <> 0 Then
                returnValue = _
                  If(SortColumns(key) = SortOrder.Ascending, _
                          compareResult, -compareResult)
                Exit For
            End If
        Next
        Return returnValue

    End Function
End Class

This class defines a comparer to use when sorting a DataRow. The sortColumns property is a Dictionary that stores the index of the column to use as the sort, and a SortOrder to define whether to sort ascending or descending. Presumably, the values for this Dictionary were obtained from the user.

The Compare method does all of the work. It compares any two DataRows to determine how each row should be sorted against any other row. The method processes each of the sortColumns.

First, it checks for a DBNull. The DBNull checking is different in the C# code and VB code. The C# code checks for a DBNull and manually sets the result of the compare. The VB code simply sets the value to Nothing if it is DBNull.

The code performs a compare based on the type of column. This is necessary because the user would expect decimals to sort as numbers, not as strings. You can add any other data types here as you require. Any data types not specifically handled will be handled as a string.

Note that a Select/Case statement was used in VB, but if/else if was used in C#. This is because C# requires its switch/case statements to switch based on constant values, not variables.

Regardless of the datatype, the result of the type-specific compare method is:

  • -1: If the value of x is less than the value of y.
  • 1: if the value of x is great than the value of y.
  • 0: if the value of x and y are equal.

If the resulting value is not 0, the loop can exit because the comparison is complete. If the resulting value is 0, meaning the columns are equal, the loop continues and the next column in the set of sort columns is checked.

You then use this class as follows.

In C#:

Dictionary<int, SortOrder> sortColumns =
                    new Dictionary<int, SortOrder>();
sortColumns.Add(2,SortOrder.Ascending);
sortColumns.Add(1, SortOrder.Descending);

RowComparer comp = new RowComparer();
comp.SortColumns = sortColumns;

var query3 = dt.AsEnumerable().OrderBy(q => q, comp);
DataView dv3 = query3.AsDataView();

In VB:

Dim sortColumns As New Dictionary(Of Integer, SortOrder)
sortColumns.Add(2, SortOrder.Ascending)
sortColumns.Add(1, SortOrder.Descending)

Dim comp As New RowComparer
comp.SortColumns = sortColumns

Dim query3 = dt.AsEnumerable.OrderBy(Function(q) q, comp)
Dim dv3 As DataView = query3.AsDataView

The first set of code sets up the Dictionary of sort columns. The sort columns are hard-coded in this example, but presumably they would come from the user.

The code then creates an instance of the new RowComparer class and passes in the set of columns.

NOTE: You could define a parameterized constructor and pass in the sorted columns instead of using a property, if desired.

The Lambda expression to perform the sort is then greatly simplified. It just passes in the desired comparer.

That’s it. You now have the ability to sort your DataTable using any user-defined set of columns.

Enjoy!

July 6, 2009

Zodiac Sign: ZodiacSigns Class

Filed under: C#,LINQ,OOP @ 4:53 pm

This entry details the implementation of the ZodiacSigns class from this example in C#:

public class ZodiacSigns : List<ZodiacSign>
{

}

Constructor

The following is the constructor defined in the ZodiacSigns class:

public ZodiacSigns()
{
    InitializeCollection();
}

This constructor ensures that the collection of zodiac signs is initialized when an instance of this class is created.

Methods

The following are the methods in the ZodiacSigns class:

public string FindSign(DateTime desiredDate)
{
    // Find the name of the Zodiak sign with the date within the ranges
    var query = from z in this
                from d in z.DateRanges
                where (desiredDate >= d.StartDate) &&
                       
(desiredDate <= d.EndDate)
                select z.Name;
    string name = query.FirstOrDefault();
    return name;
}

The FindSign method uses LINQ to find the date within the defined ranges. It returns the name of the appropriate sign.

private void InitializeCollection()
{
    // This could potentially read all of these from a file.
    // NOTE: This data may not be accurate
    this.Add(new ZodiacSign("Rat",
        new List<DateRange> {
            new DateRange(new DateTime(1996, 2,19),
                new DateTime(1997, 2,6)),
            new DateRange(new DateTime(2008, 2,7),
                new DateTime(2009, 2,25))}));
    this.Add(new ZodiacSign("Ox",
        new List<DateRange> {
            new DateRange(new DateTime(1997, 2,7),
                new DateTime(1998, 2,27)),
            new DateRange(new DateTime(2009, 2,26),
                new DateTime(2010, 2,13))}));
    this.Add(new ZodiacSign("Tiger",
        new List<DateRange> {
            new DateRange(new DateTime(1998, 2,28),
                new DateTime(1999, 2,15)),
            new DateRange(new DateTime(2010, 2,14),
                new DateTime(2011, 2,2))}));
    this.Add(new ZodiacSign("Rabbit",
        new List<DateRange> {
            new DateRange(new DateTime(1999, 2,16),
                new DateTime(2000, 2,4)),
            new DateRange(new DateTime(2011, 2,3),
                new DateTime(2012, 1,22))}));
    this.Add(new ZodiacSign("Drago",
        new List<DateRange> {
            new DateRange(new DateTime(2000, 2,5),
                new DateTime(2001, 2,23)),
            new DateRange(new DateTime(2012, 1,23),
                new DateTime(2013, 2,9))}));
    this.Add(new ZodiacSign("Snake",
        new List<DateRange> {
            new DateRange(new DateTime(2001, 1,24),
                new DateTime(2002, 2,11)),
            new DateRange(new DateTime(2013, 2,10),
                new DateTime(2014, 1,30))}));
    this.Add(new ZodiacSign("Horse",
        new List<DateRange> {
            new DateRange(new DateTime(2002, 2,12),
                new DateTime(2003, 1,31)),
            new DateRange(new DateTime(2014, 1,31),
                new DateTime(2015, 2,18))}));
    this.Add(new ZodiacSign("Sheep",
        new List<DateRange> {
            new DateRange(new DateTime(2003, 2,1),
                new DateTime(2004, 2,21)),
            new DateRange(new DateTime(2015, 2,19),
                new DateTime(2016, 2,7))}));
    this.Add(new ZodiacSign("Monkey",
        new List<DateRange> {
            new DateRange(new DateTime(2004, 1, 22),
                new DateTime(2005, 2, 8)),
            new DateRange(new DateTime(2016, 2, 8),
                new DateTime(2017, 1, 27))}));
    this.Add(new ZodiacSign("Rooster",
        new List<DateRange> {
            new DateRange(new DateTime(2005, 2, 9),
                new DateTime(2006, 1, 28)),
            new DateRange(new DateTime(2017, 1, 28),
                new DateTime(2018, 2, 15))}));
    this.Add(new ZodiacSign("Dog",
        new List<DateRange> {
            new DateRange(new DateTime(2006, 1, 29),
                new DateTime(2007, 2, 17)),
            new DateRange(new DateTime(2018, 2, 16),
                new DateTime(2019, 2, 4))}));
    this.Add(new ZodiacSign("Pig",
        new List<DateRange> {
            new DateRange(new DateTime(2007, 2, 18),
                new DateTime(2008, 2, 6)),
            new DateRange(new DateTime(2019, 2, 5),
                new DateTime(2020, 2, 21))}));
}

The InitializeCollection method does exactly what it sounds like … creating the set of date ranges associated with each zodiac sign. This code takes advantage of the list initializers now available in C#.

Enjoy!

July 3, 2009

LINQ: Defining a List of Integers

Filed under: C#,LINQ,VB.NET @ 11:39 am

Defining a list of integers in your code involves lots of tedious typing.

In VB, you can’t do this:

‘Dim numberList As new List(Of Integer) = {1, 2, 3, 4, 5, 6, 7, 8, 9}

So you have to either add numbers to the list manually, or create an array, which still involves lots of tedious typing:

Dim numberList() As Integer = {1, 2, 3, 4, 5, 6, 7, 8, 9}

With .NET 3.5, you can instead use the Range method of the Enumerable class that is part of the System.Linq namespace.

In VB:

Dim numberList2 As List(Of Integer) = Enumerable.Range(1, 9).ToList

In C#:

List<int> numberList2 = Enumerable.Range(1, 9).ToList();

The first parameter of the Range method defines the first integer of the sequence. The second parameter defines the number of items in the sequence. So Range(0,9) provides 9 integers from 0 through 8.

Enjoy!

« Previous PageNext Page »

© 2020 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs