Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Application Programming › LINQ to objects *and* logical string comparison with C#
New Posts  All Forums:Forum Nav:

LINQ to objects *and* logical string comparison with C#

post #1 of 7
Thread Starter 
LINQ: Language INtegrated Query

Perhaps one of the most powerful features of the .NET Framework, LINQ is a component released as part of .NET 3.5. Put simply, it adds data querying functionality to .NET languages and is very extensible, primarily allowing filtering and projection of data into/from arrays, enumerable objects, XML and databases (LINQ to SQL). Here we'll be focusing on one of the incarnations of LINQ, known as LINQ to Objects - which allows us to use data querying with in-memory objects, mainly with collections that implement IEnumerable.

So, what is the use of this and how can I use it? Well, I don't know about you but I like to use lists for many different things (and when I need them in memory). The .NET base class library provides a type, List(T), or List of T - where T is a generic type parameter, and it is this list type that I'll use throughout some examples. This type implements IEnumerable and IEnumerable(T) , or IEnumerable of T - this means we can use LINQ with it.

OK, so let us pretend for the moment that you have a data structure/class in your program. We'll call this class a Widget.
Code:
public sealed class Widget
{
        public string Name
        {
                get;
                set;
        }

        public int FobCount
        {
                get;
                set;
        }

        public Widget( string name )
        {
                this.Name = name;
        }

        public void Fob()
        {
                ++this.FobCount;
        }
}

This is a rather tasteless class with two auto-properties and one method. You call Widget.Fob() to fob the widget, and it increments its fob count. Let us create a class now to house all of our widgets:
Code:
public sealed class Gizmo
{
        private readonly List<Widget> _widgets = new List<Widget>();
        
        public Widget AddWidget( string widgetName )
        {
                Widget widget = new Widget( widgetName );
                this._widgets.Add( widget );

                return widget;
        }
}

Now that we've got our gizmo class to store a list of widgets, we can go ahead and create a few widgets on a gizmo. Never mind about all of this precursor stuff (we'll get to the LINQ in a bit), but it is necessary smile.gif
Code:
Gizmo gizmo = new Gizmo();

/* Create 5 "Foo" widgets and 3 "Bar" widgets */
for ( int i = 0; i < 8; ++i )
{
        gizmo.AddWidget( ( i < 5 ) ? "Foo" : "Bar" );
}

OK so our gizmo now has 8 widgets in it, excellent! Let's pretend again - the user has asked to view ALL "Foo" widgets. Let us think, how can we accomplish this? I can think of a few ways:
  1. Loop over each Widget in the list using for or foreach or List(T).ForEach and extract the ones with the name "Foo"
  2. Pass a predicate to List(T).FindAll [added for completeness only!]
  3. Use LINQ to Objects to query the IEnumerable that is our List(T).

I will show you methods 1 and 3 for comparison between the method most likely to be used and the LINQ method. First start by adding the following empty method to our Gizmo class:
Code:
public Widget[] GetWidgetsByName( string widgetName )
{
}

Loop over each Widget in the list using for or foreach or List(T).ForEach and extract the ones with the name "Foo"
The empty method might be written like so:
Code:
public Widget[] GetWidgetsByName( string widgetName )
{
        List<Widget> foundWidgets = new List<Widget>();
        foreach ( Widget widget in this._widgets )
        {
                if ( string.Compare( widget.Name, widgetName, StringComparison.OrdinalIgnoreCase ) == 0 )
                {
                        foundWidgets.Add( widget );
                }
        }

        return foundWidgets.ToArray();
}

At first glance this is not a lot of code at all, is it? Well, let's take a look at the LINQ version.

Use LINQ to Objects to query the IEnumerable that is our List(T).
Code:
public Widget[] GetWidgetsByName( string widgetName )
{
        return this._widgets.Where( widget => string.Compare( widget.Name, widgetName, StringComparison.OrdinalIgnoreCase ) == 0 ).ToArray();
}

This seemingly trivial method has been reduced to just one line using LINQ to Objects. Now would be the point at which I will introduce another feature of LINQ and C# - language extensions. Supported .NET languages actually provide some extensions to LINQ (compiler-level) that make writing the query expressions a first-class language construct. Below would be the above pure LINQ example written to use C# syntactic sugar for LINQ:
Code:
public Widget[] GetWidgetsByName( string widgetName )
{
        var query = from widget in this._widgets
                        where string.Compare( widget.Name, widgetName, StringComparison.OrdinalIgnoreCase ) == 0
                        select widget;

        return query.ToArray();
}

The advantage here is that the code (in my opinion at least) is much easier to read. I will provide all LINQ examples using this method from now on. Notice how we've directly included the string.Compare() call in our query? Thanks to the magic being done by the C# compiler, most normal code expressions (in this case those that evaluate to a Boolean) will be allowed. More information on LINQ can be found here: http://msdn.microsoft.com/en-us/library/bb397926.aspx

What's more is that the return value of these methods is an array object, which ALSO implements IEnumerable, which also means it can be used by LINQ to Objects! So we can execute additional queries against the return array to further filter our results.

But we're not done yet! I'm going to quickly show how we can implement some more methods on our Gizmo class *very* easily using the simplicitly that is LINQ to Objects.

Get all Widgets with a certain Fob count
Code:
public Widget[] GetWidgetsByFobCount( int fobMin, int fobMax )
{
        var query = from widget in this._widgets
                        where widget.FobCount >= fobMin && widget.FobCount <= fobMax
                        select widget;

        return query.ToArray();
}

Get a distinct list of Widget names from the list

You'll see below we add the Distinct() extension method to our query before calling ToArray().
Code:
public string[] GetDistinctWidgetNames()
{
        var query = from widget in this._widgets
                        select widget.Name;

        return query.Distinct().ToArray();
}

This is just a really basic introduction into the LINQ mechanism in the .NET Framework... let your mind go wild with all sorts of different operations. Here's a list of standard query operators you can use:

  • Select
  • Where
  • SelectMany
  • Sum / Min / Max / Average
  • Aggregate
  • Join / GroupJoin
  • Take / TakeWhile
  • Skip / SkipWhile
  • OfType
  • Concat
  • OrderBy / ThenBy
  • Reverse
  • GroupBy
  • Distinct
  • Union / Intersect / Except
  • SequenceEqual
  • First / FirstOrDefault / Last / LastOrDefault
  • Single
  • ElementAt
  • Any / All / Contains
  • Count

You can find documentation for these here: http://msdn.microsoft.com/en-us/library/bb345746


Logical string comparison with C#

In this small section I am going to show you how you can take advantage of a function in the Win32 API via managed code in C#. This function is only available on XP and higher and Server 2003 and higher and will not work on prior versions of Windows. What is the point of this?
Well, usually sort algorithms for strings use a computer-type sort method, whereby they treat numbers in strings as text rather than numerical content. This means that using a conventional sort method will order strings as follows:
Code:
20string
2string
3string
st20ring
st2ring
st3ring
string2
string20
string3

When presenting this sort of thing to the user interface, it does not look natural. Contrast with the natural sorting of the above strings:
Code:
2string
3string
20string
st2ring
st3ring
st20ring
string2
string3
string20

The Win32 API that produces these results is StrCmpLogicalW - and I'll show you how to wrap and use that in managed C#. The first thing we need to do is define a NativeMethods class to import the function from the unmanaged world:
Code:
public static class NativeMethods
{
        [DllImport( "Shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode )]
        public static extern int StrCmpLogicalW( string psz1, string psz2 );
}

To find out more about calling unmanaged code from the managed world, check out my thread here: http://www.overclock.net/t/1292879/how-do-i-call-an-unmanaged-library-from-managed-code/

The second thing we must do is wrap up this native method in an IComparer(T) interface implementation, and we can do this like so:
Code:
using System.Collections.Generic;
using System.Windows.Forms;

public sealed class StringComparerLogical : IComparer<string>
{
        private readonly SortOrder order;

        public StringComparerLogical()
        {
                this.order = SortOrder.Ascending;
        }

        public StringComparerLogical( SortOrder order )
        {
                /* This constructor allows the caller to specify a sort order */
                this.order = order;
        }

        #region IComparer<string> Members
        public int Compare( string x, string y )
        {
                return ( this.order == SortOrder.Descending ) 
                        ? NativeMethods.StrCmpLogicalW( x, y ) * -1 /* To reverse the sort sort we simply multiply the result by -1 */
                        : NativeMethods.StrCmpLogicalW( x, y );
        }
        #endregion
}

The integer value returned by StrCmpLogicalW, and ultimately StringComparerLogical.Compare is as follows:
  • Returns zero if the strings are identical.
  • Returns 1 if the string pointed to by X has a greater value than that pointed to by Y.
  • Returns -1 if the string pointed to by X has a lesser value than that pointed to by Y.

Many inbuilt C# functions can take an instance of an IComparer directly to sort their items - so all you need to do in those scenarios is pass in a new object of type StringComparerLogical.
In other cases, you can create a new instance of StringComparerLogical and call the Compare method manually. For example, a ListView can only compare its items using the non-generic IComparer interface. In order to make this work with a ListView you can create a class that implements IComparer and wraps your StringComparerLogical class, like so:
Code:
public sealed class ListItemComparer : IComparer
{
        private readonly int _column;
        private readonly SortOrder _order;
        private readonly StringComparerLogical _comparer;

        public int Column
        {
                get
                {
                        return this._column;
                }
        }

        public SortOrder SortOrder
        {
                get
                {
                        return this._order;
                }
        }

        public ListItemComparer()
        {
                this._column = 0;
                this._order  = SortOrder.Ascending;
                this._comparer = new StringComparerLogical( this._order );
        }

        public ListItemComparer( int column, SortOrder order )
        {
                this._column = column;
                this._order  = order;
                this._comparer = new StringComparerLogical( this._order );
        }

        #region IComparer Members
        public int Compare( object x, object y )
        {
                return this._comparer.Compare( ( ( ListViewItem ) x ).SubItems[ this._column ].Text, ( ( ListViewItem ) y ).SubItems[ this._column ].Text );
        }
        #endregion
}

Similar code can be used for other objects such as as TreeView.
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
post #2 of 7

Hey, that was an informative read. Nice tutorial showing the usage of language integrated queries in C#.  thumb.gif

This was actually helpful to me because I haven't worked much with the .NET framework. redface.gif

 

I'm not really sure of which section to put this under in the Master Sticky thread.

 
Desktop
(13 items)
 
MacBook Pro 13"
(6 items)
 
CPUGraphicsRAMHard Drive
Intel i5 480m@2.67GHz AMD Radeon Mobility 5650 4GB DDR3 500GB 
OSMonitor
Windows 7 64bit HP 15.6" 1366x768 
CPUMotherboardGraphicsRAM
E7500 Intel...:( MSI GTS250 1GB 2GB 
Hard DriveOSMonitorPower
250GB Windows XP 17" LG CRT 1280x768@85hz 400W 
CPUGraphicsRAMHard Drive
Intel i5 @ 2.5 GHz Intel HD4000 4 GB DDR3 @ 1600 MHz 500 GB @ 5400 RPM 
OSMonitor
OSX Lion 13.3" @ 1280 x 800 
  hide details  
Reply
 
Desktop
(13 items)
 
MacBook Pro 13"
(6 items)
 
CPUGraphicsRAMHard Drive
Intel i5 480m@2.67GHz AMD Radeon Mobility 5650 4GB DDR3 500GB 
OSMonitor
Windows 7 64bit HP 15.6" 1366x768 
CPUMotherboardGraphicsRAM
E7500 Intel...:( MSI GTS250 1GB 2GB 
Hard DriveOSMonitorPower
250GB Windows XP 17" LG CRT 1280x768@85hz 400W 
CPUGraphicsRAMHard Drive
Intel i5 @ 2.5 GHz Intel HD4000 4 GB DDR3 @ 1600 MHz 500 GB @ 5400 RPM 
OSMonitor
OSX Lion 13.3" @ 1280 x 800 
  hide details  
Reply
post #3 of 7
Thread Starter 
Quote:
Originally Posted by {Unregistered} View Post

Hey, that was an informative read. Nice tutorial showing the usage of language integrated queries in C#.  thumb.gif
This was actually helpful to me because I haven't worked much with the .NET framework. redface.gif

Indeed - as soon as I saw it debut in v3.5, I fell in love and have used it ever since (especially since I come from a SQL background).
Thanks!
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
post #4 of 7
This was a really good read smile.gif

Thank you so much thumb.gif
    
CPUMotherboardGraphicsRAM
Intel 3930K 4.5GHz @ 1.34V Asus rampage extreme IV Shaphire 6950 unlocked to 6970 Corsair Dominators GT 16GB 4GBX4 OCed @ 2400MHz... 
Hard DriveHard DriveOptical DriveCooling
Intel SSD 330 series 180GB WD Black 1TB 64Mb Cache + Blue 500GB 16mb Cache... LG DVD EK-Supreme HF - EN (Nickel) 
CoolingCoolingCoolingCooling
EK-FB KIT RE4 - Acetal Laing D5 Vario 12V DC Pump (MCP 655)  EK-BAY SPIN Reservoir - Plexi EK-CoolStream RAD XT (240) 
CoolingOSMonitorKeyboard
EK-CoolStream RAD XTX (120) Windows 7 64-bit LG W2261 22inch 1920X1080@60Hz Razer Lycosa 
PowerCase
Cooler Master Silent Pro Gold 1000W Thermaltake Level 10 GT 
  hide details  
Reply
    
CPUMotherboardGraphicsRAM
Intel 3930K 4.5GHz @ 1.34V Asus rampage extreme IV Shaphire 6950 unlocked to 6970 Corsair Dominators GT 16GB 4GBX4 OCed @ 2400MHz... 
Hard DriveHard DriveOptical DriveCooling
Intel SSD 330 series 180GB WD Black 1TB 64Mb Cache + Blue 500GB 16mb Cache... LG DVD EK-Supreme HF - EN (Nickel) 
CoolingCoolingCoolingCooling
EK-FB KIT RE4 - Acetal Laing D5 Vario 12V DC Pump (MCP 655)  EK-BAY SPIN Reservoir - Plexi EK-CoolStream RAD XT (240) 
CoolingOSMonitorKeyboard
EK-CoolStream RAD XTX (120) Windows 7 64-bit LG W2261 22inch 1920X1080@60Hz Razer Lycosa 
PowerCase
Cooler Master Silent Pro Gold 1000W Thermaltake Level 10 GT 
  hide details  
Reply
post #5 of 7
LINQ is a fantastic tool. I don't use it as often as I should... because it's really powerful, and not difficult to use.

Here is a very helpful tool called LINQPad: http://www.linqpad.net/
This let's you develop your LINQ queries on their own, then use them in your program, rather than having to run the entire program just to find out if a query is working.
Death Star
(20 items)
 
Darksaber
(11 items)
 
 
CPUMotherboardGraphicsRAM
Athlon II x2 245 Asus M3A78 Radeon HD6570 1GB Mushkin Silverline 2GB DDR2  
Hard DriveOptical DriveOSMonitor
OCZ Vertex 2 120GB Samsung Blu-Ray Windows 7 Samsung 46" DLP 
PowerCaseOther
Silverstone Strider Essentials 400W Silverstone Milo ML03B Hauppage WinTV 1250 
  hide details  
Reply
Death Star
(20 items)
 
Darksaber
(11 items)
 
 
CPUMotherboardGraphicsRAM
Athlon II x2 245 Asus M3A78 Radeon HD6570 1GB Mushkin Silverline 2GB DDR2  
Hard DriveOptical DriveOSMonitor
OCZ Vertex 2 120GB Samsung Blu-Ray Windows 7 Samsung 46" DLP 
PowerCaseOther
Silverstone Strider Essentials 400W Silverstone Milo ML03B Hauppage WinTV 1250 
  hide details  
Reply
post #6 of 7
Great post. +rep.
post #7 of 7
Sometimes you do need to loop through a collection, I really like the extension methods available for IEnumerable collections. Let's take a collection of Widgets.....
Code:
     var l = new List<Widget>();
        
        l.Add(new UserQuery.Widget("Alpha"));
        l.Add(new UserQuery.Widget("Beta"));
        l.Add(new UserQuery.Widget("Gamma"));
        l.Add(new UserQuery.Widget("Delta"));
        l.Add(new UserQuery.Widget("Epsilon"));
                
        l.ForEach(x => x.Fob());

Cleaner to work with (IMO)

I also have to give a huge stamp of approval to LinqPad. It is a great scratch-pad tool.
Main Rig
(15 items)
 
  
Reply
Main Rig
(15 items)
 
  
Reply
New Posts  All Forums:Forum Nav:
  Return Home
  Back to Forum: Application Programming
Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Application Programming › LINQ to objects *and* logical string comparison with C#