Thursday, October 30, 2008

Query NonGeneric Types using LINQ in C# and VB.NET

Generics was introduced in .NET Framework 2.0. Before that the System.Collections namespace consisted of collections that could store objects. After Generics became available in .NET 2.0, a lot of collection classes adopted Generics and implemented the IEnumerable interface. However there were other nongeneric types that did not support the generic versions of IEnumerable (however they did support the nongeneric version of IEnumerable).

LINQ can be used to query on objects that are generic collections and implement the IEnumerable interface. However nongeneric collections like the ArrayList do not implement the IEnumerable interface. Let us see what happens when we query nongeneric collections using LINQ.

C#

public class Cars
{
public string CarMake { get;set;}
public string CarModel { get; set; }
public int Year { get; set; }
}
class Program
{
static void Main(string[] args)
{
ArrayList carList = new ArrayList();
carList.Add(new Cars
{
CarMake="BMW", CarModel="BMW Art", Year=1978
});
carList.Add(new Cars
{
CarMake = "BMW", CarModel = "Coupe", Year = 1982
});

carList.Add(new Cars
{
CarMake = "Renault", CarModel = "Alpine", Year = 1972
});
carList.Add(new Cars
{
CarMake = "Porsche", CarModel = "Maisto", Year = 1976
});
var carQuery = from car in carList
where car.CarMake == "BMW"
select car;
}

VB.NET

Public Class Cars
Private privateCarMake As String
Public Property CarMake() As String
Get
Return privateCarMake
End Get
Set(ByVal value As String)
privateCarMake = value
End Set
End Property
Private privateCarModel As String
Public Property CarModel() As String
Get
Return privateCarModel
End Get
Set(ByVal value As String)
privateCarModel = value
End Set
End Property
Private privateYear As Integer
Public Property Year() As Integer
Get
Return privateYear
End Get
Set(ByVal value As Integer)
privateYear = value
End Set
End Property
End Class

Friend Class Program
Shared Sub Main(ByVal args() As String)
Dim carList As New ArrayList()
carList.Add(New Cars With {.CarMake="BMW", .CarModel="BMW Art", .Year=1978})
carList.Add(New Cars With {.CarMake = "BMW", .CarModel = "Coupe", .Year = 1982})
carList.Add(New Cars With {.CarMake = "Renault", .CarModel = "Alpine", .Year = 1972})
carList.Add(New Cars With {.CarMake = "Porsche", .CarModel = "Maisto", .Year = 1976})

Dim carQuery = _
From car In carList _
Where car.CarMake = "BMW" _
Select car
End Sub

In the example shown above, we have declared and populated an ArrayList with the Cars objects. We query the ArrayList using LINQ, just as we would have queried a collection implementing the IEnumerable like the Array. What do you think would be the result?

Well the code shown above will never compile at the first place. The reason being that ArrayList does not inherit from IEnumerable and hence LINQ cannot query such collections. So does that conclude that we cannot use LINQ against nongeneric collections like ArrayList? What about those class libraries that contain methods returning ArrayList? Do we scrap those methods or replace them with methods returning Generic Collections? Well without dramatizing this further, the answer is that there are ‘tricks’ using which we can query nongeneric collections using LINQ. Let us see three different ways to do so:

Method 1: By specifying the type of the variable to reflect the specific type of the objects in the collection:

By explicitly declaring the type of the variable while querying, you can cast each item in the ArrayList to reflect the specific type, in our case Cars.

C#

var cc = from Cars car in carList
where car.CarMake == "BMW"
select car;

VB.NET

Dim cc = _
From car As Cars In carList _
Where car.CarMake = "BMW" _
Select car

Observe how we are casting the ‘car’ variable to ‘Cars’.

Method 2: By using the ‘Cast’ operator.

The ‘Cast’ operator takes a nongeneric collection (that implements IEnumerable) and returns an IEnumerable. Once we get an IEnumerable, we can then query the collection using LINQ.

C#

var cc1 = from car in carList.Cast()
where car.CarMake == "BMW"
select car;

VB.NET

Dim cc1 = _
From car In carList.Cast(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

Note: The approach we followed in Method 1 is equivalent to calling Cast.

Method 3: Instead of Cast, you can also use the OfType operator.

The ‘OfType’ operator filters the elements of an IEnumerable based on a specified type. So if you have different types in your ArrayList, you get back only the type you have queried for.

C#

var cc2 = from car in carList.OfType()
where car.CarMake == "BMW"
select car;

VB.NET

Dim cc2 = _
From car In carList.OfType(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

The entire source code with the 3 different methods would look as follows:

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace QueryArraylistWithLINQ
{
public class Cars
{
public string CarMake { get;set;}
public string CarModel { get; set; }
public int Year { get; set; }
}

class Program
{
static void Main(string[] args)
{
ArrayList carList = new ArrayList();
carList.Add(new Cars
{
CarMake = "BMW",
CarModel = "BMW Art",
Year = 1978
});

carList.Add(new Cars
{
CarMake = "BMW",
CarModel = "Coupe",
Year = 1982
});

carList.Add(new Cars
{
CarMake = "Renault",
CarModel = "Alpine",
Year = 1972
});

carList.Add(new Cars
{
CarMake = "Porsche",
CarModel = "Maisto",
Year = 1976
});

var cc = from Cars car in carList
where car.CarMake == "BMW"
select car;
var cc1 = from car in carList.Cast()
where car.CarMake == "BMW"
select car;
var cc2 = from car in carList.OfType()
where car.CarMake == "BMW"
select car;

foreach (Cars c in cc1)
Console.WriteLine(c.CarMake + "-" + c.CarModel);
Console.ReadLine();
}
}
}

VB.NET

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Collections

Namespace QueryArraylistWithLINQ

Public Class Cars

Private privateCarMake As String
Public Property CarMake() As String
Get
Return privateCarMake
End Get
Set(ByVal value As String)
privateCarMake = value
End Set
End Property
Private privateCarModel As String
Public Property CarModel() As String
Get
Return privateCarModel
End Get
Set(ByVal value As String)
privateCarModel = value
End Set
End Property
Private privateYear As Integer
Public Property Year() As Integer
Get
Return privateYear
End Get
Set(ByVal value As Integer)
privateYear = value
End Set
End Property
End Class

Friend Class Program

Shared Sub Main(ByVal args() As String)
Dim carList As New ArrayList()
carList.Add(New Cars With {.CarMake = "BMW", .CarModel = "BMW Art", .Year = 1978})
carList.Add(New Cars With {.CarMake = "BMW", .CarModel = "Coupe", .Year = 1982})
carList.Add(New Cars With {.CarMake = "Renault", .CarModel = "Alpine", .Year = 1972})
carList.Add(New Cars With {.CarMake = "Porsche", .CarModel = "Maisto", .Year = 1976})

Dim cc = _
From car As Cars In carList _
Where car.CarMake = "BMW" _
Select car

Dim cc1 = _
From car In carList.Cast(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

Dim cc2 = _
From car In carList.OfType(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

For Each c As Cars In cc1
Console.WriteLine(c.CarMake & "-" & c.CarModel)
Next c

Console.ReadLine()
End Sub
End Class
End Namespace


It’s a good decision on your part if you convert your nongeneric collections to generic ones to gain performance and strong type checking. However, meanwhile if you happen to use any nongeneric collections in your code and want to query them using LINQ, you can use the methods as discussed in this article. I hope this article was useful and I thank you for viewing it.

No comments: