Projection, Anonymous Types, and Select

The first LINQ method we'll dive into is Select.

  • Select is used to transform one type into another, essentially making something out of something else.
  • It is used by LINQ for projection, where you can change the shape of the data you’re working with.

What Does Select Do?

  • It takes each element in a collection, calls the transformer function, and returns the resulting elements.
  • Common transformations include constructing new objects, modifying data, or projecting specific properties.

Example

Let’s say you have a collection of Employee objects, and you want to project their Name and Salary properties into a new type. Here’s an example:

var employeeData = employees.Select(e => new { 
  e.Name, 
  e.Salary
});

Wait, what the heck is that thing? We're using new without a type? They're known as anonymous types, and they're supremely useful.

Anonymous Types

Anonymous types are types that have no defined class.

They are types that are created dynamically, allowing you to group multiple properties together without declaring an explicit type for the data.

Anonymous types have a few unique features.

  • The properties of anonymous types are read-only.
  • Anonymous types automatically implement Equals and GetHashCode based on their structural properties, meaning two anonymous objects with the same values are considered equal. (Exactly like records in fact. You could say that anonymous types were the original record types in C#.)
var employee1 = new { Name = "John", Salary = 120000 };
var employee2 = new { Name = "John", Salary = 120000 };

Console.WriteLine(employee1.Equals(employee2));  // Output: True

Why is this useful? Why not just use the original type as is?

If you have a complex type with lots of nested data, you can project a new type to "flatten" the data, like so:

var employeeData = employees.Select(e => new { 
  ManagerName = e.Manager.Name,
  e.Salary,
  DepartmentName = e.Department.Name
});

You might need a completely different object from your first type:

var employeeData = employees.Select(e => new EmployeeInfo(e));

Spoiler alert, in the next course we'll be using LINQ to query databases, and we can improve performance by projecting a new type that has fewer columns.

When to Use Anonymous Types

  • Use them when you need a quick data projection without creating new classes.
  • Typically used in temporary situations like LINQ queries where the projected result is not intended to last beyond its immediate use.

Browse Source Code

You can browse the source code of LINQ to see how Select is implemented. LINQ methods, including Select, are built on top of extension methods and leverage IEnumerable. Here's a simplified version of how Select might look:

public static IEnumerable<TResult> Select<TSource, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, TResult> selector)
{
    foreach (TSource element in source)
    {
        yield return selector(element);
    }
}

Things to Avoid

Avoid using Select as a kind of foreach loop: Although it may seem tempting, Select is not a substitute for foreach. Select should be used for projection (i.e., transforming data), not for performing actions or side effects.

//Incorrect Usage
employees.Select(e => Console.WriteLine(e.Name));  //🤢

This misuses Select as if it were foreach. If you need to iterate over a collection to perform an action, use foreach instead.

Another common mistake is using Select to modify the objects that are being iterated over. Select should be used to transform and project new objects, not mutate existing ones.

//Incorrect Usage
employees.Select(e => e.Salary += 5000);  //🤢

This example tries to modify each employee’s salary, which is not the intended purpose of Select. If you need to mutate objects, handle that logic elsewhere.