- Table of Contents
- .NET Book Recommendations
- Getting Started with .NET
- The Microsoft .NET Framework
- The Common Language Runtime (CLR), the Common Type System (CTS), and the Common Language Specification (CLS)
- .NET Framework Class Library
- Visual Studio .NET
- .NET Enterprise Servers and .NET My Services
- .NET Compliant Languages
- C#
- Visual Basic .NET (VB .NET)
- ASP.NET
- XML Web Services
- ADO.NET
- XML.NET
- Windows Forms
- Why .NET?
- Displaying Errors with the Error Provider
- COM Interoperability
- Comparing Java and .NET
- Calling Unmanaged Code
- .NET Application Security
- Code Access Security
- .NET Standards Support
- Numeric Types in the .NET Framework
- Working with Strings
- Formatting Strings
- Trimming Character Strings
- Comparing Strings in .NET 2.0
- Arrays and Collections
- Arrays as Class Members
- Sorting a Multi-Dimensional Array
- Sorting a Multi-Dimensional Array with LINQ
- File I/O (System.IO)
- Working with File Names
- Using the File System
- Working with Files and Directories
- Monitoring the File System
- Working with Streams
- Working with Text Encodings
- Working with Date and Time
- Extending the DateTime Class
- Using DateTimeOffset
- Fun with Dates
- Exceptions
- Delegates
- Events
- Asynchronous Programming
- Asynchronous File I/O
- Timers
- Random Numbers
- Cryptographically Secure Random Numbers
- Serialization
- MultiThreading (System.Threading)
- Multi-Threading Overview
- The Managed Thread Pool
- Managed Threading
- Thread Synchronization
- Synchronizing Data Access
- Trace Debugging
- Tracing in .NET 2.0
- ASP.NET Trace
- Validating User Input in ASP.NET Web Pages
- Event Logging
- Monitoring Application Performance
- Accessing the Registry
- Accessing Environment Information
- Environment Variables in .NET 2.0
- Managing Windows Forms Applications
- Working with Email
- Working with Graphics
- Animating a Background
- Working with Images
- Drawing Cycloid Curves
- Simulating the Spirograph
- Building International Web Applications
- .NET Compact Framework
- Mobile Web Development with ASP.NET
- Speech Technologies
- Microsoft MapPoint Web Service
- Working with Typed DataSets
- Using Relationships in DataSets
- DataColumn Expressions
- Playing Simple Sounds
- Playing Sounds with .NET 2.0
- Returning an Image in a Web Page
- RSS
- Best Practices Project Structure
- Best Practices Application Blocks
- The Data Access Application Block
- The Exception Management Application Block
- Best Practices — Performance
- Best Practices — Performance and Scalability
- Best Practices - Testing
- Reading the Tea Leaves, 2005
- Predictions: A Look Back at 2005, and a Look Ahead to 2006
- .NET Downloads
- Application Deployment Overview
- Application Deployment — Versioning
- Application Deployment — Version Policy
- Application Deployment — Packaging and Distribution
- .NET Remoting Overview
- A Remoting Demonstration
- Remoting Configuration
- Remoting: Lifetimes and Leases
- Remoting: Other Issues
- Attributes
- Writing Custom Attributes
- Accessing Attributes in Code
- Reflection
- Class Design: Inheritance, Interface, or Composition?
- The TriTryst Game
- Console Applications in .NET 2.0
- New File I/O Methods in .NET 2.0
- Building Projects with MSBuild
- Unmanaged Callbacks in .NET 2.0
- Timer Troubles
- Non-Rectangular Windows Forms
- Windows Forms Transparency
- 10 Things I Hate About Visual Basic
- 10 Things I Hate About C#
- Background Processing with Idle Time
- Scaling Windows Forms
- Reading and Writing Binary Data
- New Memory Management Functions in .NET 2.0
- Compatibility Between .NET 1.1 and .NET 2.0
- Managed Debugging Assistants in .NET 2.0
- XDir: A Program for Viewing Directory Sizes
- The Microsoft.VisualBasic Namespace
- Operator Overloading
- Working with GPS Data
- Hidden Visual Studio Tools
- .NET 3.0
- The .NET 2.0 Stopwatch Class
- Nullable Types
- Drawing Rotated Text
- Unsafe Code
- Other .NET Languages
- Compiler Directives
- Safe Handles
- Predictions, 2007 Edition
- New Features in C# 3.0
- Generics
- Network Client Programming
- On the Misuse of Exceptions
- Maximum Object Size in .NET
- More on Maximum Object Sizes
- Keyed Collection Memory Limitations
- Matching String Endings
- Allocating Small Data Structures
- Grumbling About Limitations
- Some Thoughts on the Nature of What We Do
- Working with Predicates in Collections
- Working with DataReaders
- Outputting XML with XmlWriter
- Writing XML Data
- Working with Compression
- Another Look at Compressed Streams
- Compressing a Very Large File
- Canonical URIs
- Constructing URIs
- Using OneWayAttribute for Remote Calls
- Selecting a Garbage Collector
- Linked List
- Linked List Application - The MRU List
- Auto-implemented Properties in C#
- The HashSet Collection
- Looking Ahead: 2018
- An Experiment in Optimization
- A Larger Integer
- Extension Methods
- Language Integrated Query (LINQ)
- Variable Length Parameter Lists
- The ReaderWriterLockSlim Synchronization Primitive
- Sorting a Text File
- Sorting a Large Text File
- Using ListView with Large Data Sets
- LINQ One-Liners
- Regular Expression Optimization
- Random File I/O
- Computing the Size of a Structure
- More on Computing Structure Sizes
- UnmanagedMemoryStream
- Dynamically Loading Code
- Building a String Table
- Delegates Versus Function Pointers
- Visual Studio Editor Features
- A Simple Profile Timer
- New Features in C# 4.0
- IEnumerator or IList?
- New Features in .NET 4.0
- Set Operations with IEnumerable and HashSet
- Using File Locks
- Extending Object Functionality
- Clearing a HashSet
- When Hash Codes Matter
- Parsing Command Line Options
- Creating a Single-Instance Program
- Asynchronous Windows Forms Events
- The BackgroundWorker Component
- Fixing a Dumb Mistake
- Thinking About Multi-Threaded Programs
- JavaScript Object Notation
- Better JSON Processing with JSON.Net
- Useful .NET-related Sites
- Markov Models
- Building an Order 0 Markov Model
- Higher Order Markov Models
- Webmaster's Guide to robots.txt
- An Overview of the Parallel Extensions to .NET
- Parallel Extensions Synchronization Objects
- Thread Safe Collections
- A Bug and a Conundrum
- Another Bug and an Answer
- Task Parallel Library
- Good and Bad Ideas in C#
- Parallel LINQ
- Copying Large Files
- Replacing File.Copy
- Learning from Our Mistakes
- Symbolic Links
- There Is No Easy Fix
- Tracking Hurricanes
- Examining Hurricane Data
- Searching for Multiple Strings
- Simple JSON Processing
- Aho-Corasick String Searching
- Writing a Web Crawler
- Web Crawler Politeness
- Source Control Management
- Subversion
- Communicating with Datagrams
- Fun with Actions and Funcs
- The Future of Media
- The Importance of Metadata
- Of Comparison and IComparer
- IComparer, Comparer, IComparable, Oh My!
- Comparing Generic Types
- A Simple HTTP Server
- Quantizing DateTime Fields
- More Fun with the Garbage Collector
- Refactor, Don't Rewrite
- A Generic BinaryHeap Class
- A Generic File Sorter
- Birthdays, Random Numbers, and Hash Keys
- Random Selection from Large Groups
- Command Line Tools for Windows
- Reading and Writing, Bit by Bit
- Selecting the Top N Items from a Group
- Determining Website Content Encoding
- Benefits and Drawbacks of Syndication
- Pubsubhubbub
- Memory Use Misconceptions
- Risk, Lost Opportunity, and Other Hidden Upgrade Costs
- Culture Shock: from .NET to JavaScript
- Using .NET for a Startup
- Tracking Wikipedia Changes with IRC
- Browser Applications and the Same Origin Policy
- Handling the Unexpected
- Dealing with Growth
- Deleting the Oldest File
- Where Do I Put Stuff?
- .NET Timer Resolution
- Exploring Options for Better Timers
- Using the Windows Timer Queue API
- Locks Aren't Slow
- Alternatives to Locks
- Lock Free Concurrent Collections
- The BlockingCollection Class
- Customizing BlockingCollection
- What Time Is It? Daylight Saving Time and Computers
- Using enums to Save Memory
- New File Operations in .NET 4.0
- Building a Hierarchy of Rectangles
- A Faster File Copy
- Constants Are Forever
- The Dangers of Floating Point
- Goto is Not Inherently Evil
- The Weakest Link
- Reducing Memory Required for Strings
- Grouping with LINQ
- HttpListener "Gotchas"
- Extension Methods Are Evil
- Finding the Registered Domain in a URL
- Drawing Text
- Obfuscating Sequential Keys
- Properties of Obfuscated Keys
- Finding Changes Between Two Lists
- Using the ConcurrentBag Collection
- Never Sleep!
- Shuffling and Sorting
- Viewing Large Text Files
- Use the Right Tool
- Why GetHashCode Matters
- Optimization Guidelines
- Timer Differences
- The Mutex
- Modifying a Working System
- Building a New Type of Stream
- More Large File Problems
- A Better File.Copy Replacement
- Throwing the Wrong Exception
- Approximate Counters
- Monitoring a Timer
- Combining Consoles and Forms
- Embedding a Text Resource
- Handling Concurrent Downloads
- The Importance of Domain Knowledge
- Stupid Programmer Tricks
- Aho-Corasick Revisited
- Expressiveness is the Soul of Brevity
- Fun with Anonymous Types
- Simplifying a Multi-Threaded Application
- Work Smarter
- The Skip List Data Structure
- A More Memory-Efficient Skip List
- Selection Revisited
- Why Async?
- What the Future Holds
- The "Roslyn" CTP
- Where We've Been
- Informit Reference Library
Sorting a Multi-Dimensional Array with LINQ
Last updated Dec 18, 2009.
In Sorting a Multi-Dimensional Array, I showed how to use the IComparer interface to sort multi-dimensional arrays--something that the built-in Array.Sort method can't do. That article was written several years ago, before the introduction of generics and before LINQ. Whereas it's easy enough to modify the example code so that it works with generics, getting LINQ to sort those arrays seems to stump some people.
Sorting a jagged array
As I pointed out in the original article, .NET supports two types of arrays: jagged arrays and rectangular arrays. And although only rectangular arrays are CLR compliant, many .NET programs still use jagged arrays because in many cases they are more efficient and easier to use.
A jagged array is, in essence, an array of arrays. In C#, the declaration is very simple:
// declare and initialize a jagged array
int[][] a = new int[][]
{
new int[]{33, 12},
new int[]{12, 10},
new int[]{13,5},
new int[]{27, 19}
};
If there's any doubt about the "array of arrays" characterization, take a look at the corresponding Visual Basic code.
' declare and initialize a jagged array
Dim a1() As Integer = {33, 12}
Dim a2() As Integer = {12, 10}
Dim a3() As Integer = {13, 5}
Dim a4() As Integer = {27, 19}
Dim a()() As Integer = {a1, a2, a3, a4}
I keep thinking that there should be a cleaner way to do that in Visual Basic, but my VB skills are somewhat lacking. In any case, it's clear that this creates an array of arrays.
In the original article, I showed how to sort this array by passing an IComparer to the Array.Sort method. Whereas that option is still available, changes in .NET and in the C# and Visual Basic languages give several other ways to approach the problem.
One way (and probably the simplest) to approach the problem is to call the Array.Sort overload that takes a generic Comparison delegate as a Lambda expression. Doing so turns the sorting into a one-liner. For example, this code sorts the array based on the second item in each of the subarrays:
[C#]
Array.Sort(a, (a1, a2) => { return a1[1].CompareTo(a2[1]); });
That is, the C# code is a one-liner because C# allows anonymous methods. Since Visual Basic doesn't support anonymous methods, you have to create the Lambda expression prior to calling Array.Sort. It's another line of code, but at least it keeps the comparison function right there with the sort call:
[Visual Basic]
Dim sorter As Comparison(Of Integer()) = Function(x() As Integer, y() As Integer) x(1).CompareTo(y(1)) Array.Sort(a, sorter)
Given the above code that uses a Comparison delegate, the syntax to make LINQ sort the array just falls into place:
[C#]
var sorted = from x in a
orderby x[1]
select x;
[Visual Basic]
Dim sorted = From x In a _
Order By x(1) _
Select x
The result of the LINQ sort is somewhat different from what you'd expect. First of all, it does not sort the array in place. The source array is left in its original condition. In addition, the sorted object is not an array. At least, it can't be addressed as an array. In Visual Basic, the type reported by sorted.GetType() is System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.Int32[],System.Int32[]]. In C#, the reported type is System.Linq.OrderedEnumerable`2[System.Int32[],System.Int32].
It appears that in Visual Basic you can access the resulting collection (sorted) as though it's an array. For example, the line Console.WriteLine(sorted(0)(1)) does what you expect: it outputs the second element in the first subarray.
In C#, you cannot address the resulting collection as though it's an array. That is, trying to access sorted[0][1] will generate an error at compile time.
If you want to make sure that you get an array back, just call sorted.ToArray().
If you want to sort the array in place, call Array.Sort, passing it the comparison function.
If you don't want to affect the original array, have LINQ do the sorting. And if you want a copy of the array, as an array, call ToArray on the result that you get back from LINQ.
Sorting a rectangular array
As I pointed out in the original article, sorting a rectangular array has to be done indirectly. Since the entire array is one contiguous block of memory, there is no way to sort the rows. Instead, you have to create a tag array that contains row indices into the original array, and sort that tag array. None of the changes to .NET over the years have changed what you have to do, but language changes have simplified it somewhat.
Let's start by defining the arrays.
[C#]
int[,] a = new int[,] { { 33, 12 }, { 12, 10 }, { 13, 5 }, { 27, 19 } };
int[] tagArray = new int[] { 0, 1, 2, 3 };
[Visual Basic]
Dim a(,) As Integer = {{33, 12}, {12, 10}, {13, 5}, {27, 19}}
Dim tagArray() As Integer = {0, 1, 2, 3}
The advent of Lambda expressions (and, for C#, anonymous methods) removes the need to create an object that implements the IComparer interface. Instead, we can create a Lambda expression and call the Array.Sort method that takes a Comparison delegate:
[C#]
Array.Sort(tagArray, (t1, t2) => { return a[t1, 1].CompareTo(a[t2, 1]); });
[Visual Basic]
Dim sorter As Comparison(Of Integer) = Function(t1 As Integer, t2 As Integer) a(t1, 1).CompareTo(a(t2, 1)) Array.Sort(tagArray, sorter)
Remember, this doesn't actually sort the array, but rather the tag array. To access the array in sorted order, you have to do it indirectly--through the tag array:
[C#]
foreach (var i in tagArray)
{
for (int j = 0; j < 2; ++j)
{
Console.Write("{0}, ", a[i, j]);
}
Console.WriteLine();
}
[Visual Basic]
For Each i In tagArray
For j As Integer = 0 To 1
Console.Write("{0}, ", a(i, j))
Next
Console.WriteLine()
Next
Again, the LINQ code to sort the tag array is very simple:
[C#]
var sorted = from x in tagArray orderby a[x, 1] select x;
[Visual Basic]
Dim sorted = From x In tagArray Order By a(x, 1) Select x
Again, in order to access the array in sorted order, you have to do it indirectly through the tag array.



