Content:
1. Benefits of using LINQ - primitive types
   - Filtering
   - Sorting
2. Using LINQ with classes
   - Filtering
   - Grouping
   - Grouping with math functions
   - Grouping by multiple items
   - Using Grouping to select a disctint set
   - Distinct
3. Single, First and FirstOrDefault
4. How to write for each
5. Multiple sets manipulation
   - Joining 2 sets
   - Left outer join
   - Combining sets
   - SelectMany
6. Zip
7. Aggregate
8. OfType
9. Using let
10. Debuging LINQ query


101 examples on MSDN

Before you start using LINQ, you need to include proper namespace by: using System.Linq;


1. Benefits of using LINQ - primitive types

Imagine you have an array of text codes like below

C#
string[] codes = new string[] { "abc", "def", "ddf" };
and you want to select a subset of the elements in the array. You can write a code like this:

C#
List<string> selection = new List<string>(); 
foreach (string s in codes)
{
    if (s.StartsWith("d")) selection.Add(s);
}

Filtering

With LINQ, you can rewrite your foreach loop as below. You have 2 options.

C#
var selection = (from s in codes where s.StartsWith("d") select s).ToList();

var selection = codes.Where(s => s.StartsWith("d")).ToList();

Sorting

You will get even more from LINQ if you need to sort the selection. In this case by second and then first character in each code.

C#
var selection = (from s in codes where s.StartsWith("d") orderby s[1],s[0] descending select s).ToList();

var selection = codes.Where(s => s.StartsWith("d")).OrderBy(s => s[1]).ThenByDescending(s => s[0]).ToList();

2. Using LINQ with classes

Usage of LINQ is not limited to primitive types like string or integers. You can use LINQ with classes as well.

C#
//define a class that represents person 
class Person
{
    public string DepartmentId;
    public string Name;
    public int Age;
}

// populate people
List<Person< people = new List<Person>() { 
                new Person() { DepartmentId = "IT", Name = "John", Age=32 }, 
                new Person() { DepartmentId = "FI", Name = "Paul", Age=43 }, 
                new Person() { DepartmentId = "FI", Name = "George", Age=28 } 
};

Filtering

C#
// select whole classes from the list
List<Person> itPeople = people.Where(p => p.DepartmentId == "IT").ToList();

// if you want only people names
List<string> itPeopleNames = people.Where(p => p.DepartmentId == "IT").Select(p => p.Name).ToList();

Grouping

and you can group objects as well . Be aware that the result is different from what you would get in SQL, the result structure is a tree.

C#
var groups = from p in people group p by p.DepartmentId into g select new { MyKey = g.Key, MyGroup = g };

var groups1 = people.GroupBy(p => p.DepartmentId).Select( g => new { MyKey = g.Key, MyGroup = g });

// iterate over result
foreach (var gr in groups1)
{
    string key = gr.MyKey;
    foreach (var p in gr.MyGroup)
    {
        Person person = p;
    }
}

Grouping with math functions

or use mathematical functions for groupped data.

C#
var averages = from p in people group p by p.DepartmentId into g select new { MyKey = g.Key, AvgAge = g.Average(p => p.Age) }; 

var averages1 = people.GroupBy(p => p.DepartmentId).Select(g => new { MyKey = g.Key, MyAvg = g.Average(p => p.Age) });

Grouping by multiple items

or use mathematical functions for groupped data.

C#
var averages2 = people
    .GroupBy(p => new { p.DepartmentId, p.Age })
    .Select(g => new { DeptId = g.Key.DepartmentId, Age = g.Key.Age, Salary = g.Average(p => p.Salary) });

Using Grouping to select a disctint set

C#
var disctincList = people
  .GroupBy(p => new { p.Name, p.DepartmentId })
  .Select(g => g.First());

Distinct

or select distinct set of values.

C#
IEnumerable<Person> disctinctSet = people.Distinct();

IEnumerable<Person> disctinctSet1 = (from p in people select p).Distinct();

3. Single, First and FirstOrDefault

C#
First - returns first element of sequence. Throws exception when the sequence is empty
FirstOrDefault - returns first element of sequence. If the sequence is empty, returns an empty object
Single - returns first element of sequence. Throws exception when the sequence is empty or has more than 1 element

4. How to write for each

Although using "foreach" provides easy ability to debug in Visual Studio

C#
foreach (var p in persons)
{
    p.LastName = "John";
}
it is possible to see foreach written in LINQ as below

C#
persons.Select(p => { p.LastName = "John"; return p; } ).ToList();

persons.All(p => { p.LastName = "John"; return true; } ).ToList();

5. Multiple sets manipulation

Joining 2 sets

You can use LINQ to join 2 sets similarly like in SQL. You may define the class that will hold combined result (but it not necessary).

C#
// class that presents a departement 
class DepartmentO
{
    public string DepartmentId;
    public string Name;
}

// populate departments
List<DepartmentO> departments = new List>DepartmentO>()
{
    new DepartmentO() { DepartmentId = "IT", Name = "Information Technology"},
    new DepartmentO() { DepartmentId = "FI", Name = "Finance"},
};

// class that presents combined result for person and departement 
class PersonWithDepartment
{
    public string DepartmentId;
    public string PersonName;
    public string DepartmentName;
}
Now you can populate both sets and combine result with LINQ.

C#
// join two object sets - option 1
IEnumerable<PersonWithDepartment> joinedSet = from d in departments
                                             join p in people on d.DepartmentId equals p.DepartmentId
                                             select new PersonWithDepartment()
                                             {
                                                 DepartmentId = p.DepartmentId,
                                                 PersonName = p.Name,
                                                 DepartmentName = d.Name
                                             };

// join two object sets - option 2
IEnumerable<PersonWithDepartment> joinedSet2 = people.Join(
    departments,
    (p) => (p.DepartmentId),
    (d) => (d.DepartmentId),
    (p, d) => new PersonWithDepartment()
    {
        DepartmentId = p.DepartmentId,
        PersonName = p.Name,
        DepartmentName = d.Name
    }
);

Left outer join

In same cases you need to do LEFT OUTER JOIN, which means that people that do not belong to any department will be included in the result.

C#
var outerJoinSet =  from p in people
                    join d in departments on p.DepartmentId equals d.DepartmentId into d1
                    from d2 in d1.DefaultIfEmpty()
                    select new 
                    {
                        DepartmentId = p.DepartmentId,
                        PersonName = p.Name,
                        DepartmentName = d2.Name
                    };

Combining sets

You can combine or extend existing sets.

C#
// create NEW set as union of two sets
List<Person> union = people.Union( 
    new List<Person>() {new Person() { DepartmentId = "IT", Name = "George" }}).ToList();

// add people to existing set
people.AddRange(
    new List<Person>() { new Person() { DepartmentId = "IT", Name = "George" } });
    
    
// concatenate existing 2 lists - the original lists remain untouched, no new Person element gets allocated
var list1 = new List<PersonO>() { new PersonO() { DepartmentId = "IT", Name = "John" } };
var list2 = new List<PersonO>() { new PersonO() { DepartmentId = "FI", Name = "George" } };
List<PersonO> concatenated = list1.Concat(list2).ToList();

SelectMany

C#
// cross join
var list1 = new string[] { "1", "2", "3" };
var list2 = new string[] { "A", "B" };

var result1 = list1.SelectMany(l1 => list2, (l1, l2) => new { l1, l2 });

// result: {1,A}, {1,B}, {2,A}, {2,B}, {3,A}, {3,B}


// list of lists flattening
var masterList = new List>string[]>() { list1, list2 };
var result2 = masterList.SelectMany(i => i);
// result 2: 1,2,3,A,B

6. Zip

C#
string[] list1 = { "a", "b", "c" };
string[] list2 = { "X", "Y" };

var zipped = list1.Zip(list2, (i1, i2) => i1 + i2);

foreach (var item in zipped)
{    
   Console.WriteLine(item);
}
// Output

// aX
// bY

7. Aggregate

or aggregate elements, e.g. calculate factorial or concatinate strings .

example with comparison for-each and aggregate

C#
var input = new []{1,2,3,4,5};
var result = input.Aggregate((a,b) => a * b);

// you can seed initial value
var result = input.Aggregate(10, (a,b) => a * b);

8. OfType

C#
System.Collections.ArrayList objects = new System.Collections.ArrayList(2);
objects.Add("First");
objects.Add(1);
IEnumerable<string> strings = objects.OfType<string>();

9. Using let

Sometimes it is necessary to convert data before they reach filter and sort part of the query.

C#
var list = new string[] { "abc_1", "def_11", "xyz_3" };
var result =
	from item in list
	let number = Convert.ToInt16(item.Split('_')[1])
	where number > 2
	orderby number
	select item;

result = list
	.Select(item => new { Item = item, Code = Convert.ToInt16(item.Split('_')[1]) })
	.Where(i => i.Code > 2)
	.OrderBy(i => i.Code)
	.Select(i => i.Item);
	
// result: xyz_3, def_11 	

10. Debuging LINQ query

When it is necessary to debug a LINQ query if the where condition is more complicated that the one in this example.

C#
var numbers = new int[] { 1, 2, 3 };

var result = numbers
	.Where(n => n > 2);
It can be done without rewriting to foreach loop, but some code injection is necessary.

C#
var result1 = numbers
	.Select(
		item =>
		{
			return item; // set breakpoint here - you will see 1,2,3
		})
	.Where(n => n > 2)
	.Select(
		item =>
		{
			return item; // set breakpoint here - you will see 3
		});

// the breakpoint will be hit when code reaches ToList()
var result1debug = result1.ToList();
If you want to examine the the evaluation of the where condition

C#
var result2 = numbers
	.Where( n =>
		{
			bool b = n > 2;
			return b; // set breakpoint here 
		});

var result2debug = result2.ToList();