LINQ (Language Integrated Query)

LINQ is a set of technologies that allow simple and efficient querying over different kinds of data. It allows filtering, ordering and transforming the collection elements, and more.

LINQ can work with other types of collections like databases or XML files and syntax remains the same.

Most of the LINQ methods are extension methods for IEnumerable<T>, that means we can use them with any type that implements IEnumrable<T> interface. Another thing to note about IEnumrable<T> is that it doesn’t expose any method for collection modification. When you see some method such as Append, the collection object will not be modifed. A new collection will be created instead.

Deferred Execution

Deferred execution means that the evaluation of a LINQ expression is delayed until the value is actually needed. It improves performance by avoiding unnecessary execution.

var words = new List<string> { "a", "bb", "ccc", "dddd" };
var shortWords = words.Where(w => w.Length < 3);

Console.WriteLine("First iteration");
foreach(var word in shortWords)
{
    Console.WriteLine(word);     // will print a,bb
}

words.Add("e");

Console.WriteLine("Second iteration");
foreach(var word in shortWords)
{
    Console.WriteLine(word);    // will print a,bb,e
}

LINQ defines querys to retrive data. We can force the materialization of data by, for example, using the ToList method. In the above code, if we say var shortWords = words.Where(w => w.Length < 3).ToList(), then shortWords will be a list, not a query, and the 2 printed results will be the same.

Any

Check if ANY element in the colletion matches the given criteria. It will take a function predicate as parameter, so we can pass a lambda here. We can also skip the parameter of the Any method to simply check if the collection is not empty. E.g.. bool isNotEmpty= someCollection.Any(). It will return true if collection is NOT empty.

All

check if ALL elements in the collction mathcing the given criteria.

Count/LongCount

Boththese methods are used to count the elements of the collection that match some given predicate. LongCount is used to return a long if we think the result could be larger than the max of int.

Contains

Check if a given element is present in the collection.

OrderBy, OrderByDescending, ThenBy, ThenByDescending

OrderBy: Order the collection by some criteria.
OrderByDescending: Similar to OrderBy but in descending order.
ThenBy: If we order a collection of objects by some property, if the property is the same, we want to order them by another property. So after we use OrderBy or OrderByDescending, we can call ThenBy or ThenByDescending. We can chain as many ThenBy methods as we want if we need to order by multiple criteria.

First/Last/FirstOrDefault/LastOrDefault

The First method returns the first element from the collection. If a predicate is given, it returns the first element that matches this predicate. Last method likewise. Both of the 2 methods will throw an exception if the collection is empty or when there’are no elements that match the given predicate.

FirstOrDefault/LastOrDefault will return the default value for the type stored in the colleciton if there is no first or last element that matches the given criteria.

Where

Filter the collection based on a predicate. Where can take 2 types of predicates: One with Func<TSource, bool>, and the other one is Func<TSource, int, bool> where int is the index of the element.

Distinct

Remove all duplicated values from the collection, and return a collection of unique elements.

Select

Select projects each element of a collection into a new form. This means that with the lambda expression, we define an operation that will be applied to each element of the collection. The aggregated results of those operations will create a new collection. A great thing about the Select method is that it allows us to change the type of the collection.

Average

Get the average value of collection of items.

Anonymous Types

Anonymous types are types without names. They’re typically used when we need to create objects, having some specific set of properties, but if we don’t want to define a dedicated type for them because we don’t intend to use this type anywhere else.

List<List<int>> listsOfNumbers = new List<List<int>>
{
    new List<int> { 1, 2, 3, 4, 5, 6},
    new List<int> { 7, 8, 9, 10, 11},
};

var result = listsOfNumbers
    .Select(listOfNumbers => new
    {
        Count = listOfNumbers.Count(),
        Average = listOfNumbers.Average()
    })
    .OrderByDescending(countAndAverage => countAndAverage.Average)
    .Select(countAndAverage => $"Count is: {countAndAverage.Count}, Average is: {countAndAverage.Average}");

Anonymous type objects are created using object initializer that contain the list of properties and their values. We can name the properties anything we want. We only use this type within the scop[e of this LINQ query. But don’t use anonymous types for types we do want to reuse.

LINQ queries are the places where the anonymous types are most often used. But still we can use them anywhere we want.

var pet = new { Name = "Jack", Type = "People" };

All properties in anonymous types are readonly. It’s not used often, and normarlly we use it to carry some temp data between LINQ queries.