Must-Know Concepts Related to LINQ and IEnumerable
Introduction
Our previous article (.NET Nakama, 2022 March) taught us about the LINQ architecture and technologies, query syntaxes, etc. As we saw, we can define a generic class by using the <T>
sign after the class name, e.g. MyClassName<T>
. The T
is the generic type parameter in which we can use any word (e.g., TKey, TCustomer, etc.). Commonly, the name of the generic type parameter starts with a T
to clearly show that it’s a type. We can use a generic class or method for different types without the cost of boxing operations and the risk of runtime casts.
In addition, we saw that the IEnumerable<T>
interface enables the generic collection classes to be enumerated using the foreach
statement. Therefore, the LINQ sources implement the IEnumerable
interface to be enumerated. Moreover, we saw how to use extension methods and lambda expressions in the LINQ method syntax.
In the previous paragraphs, several concepts need further explanation. Therefore, this article will focus on filling the puzzle about Boxing and Unboxing, Extension Methods, Anonymous Functions, Lambda Expressions, Closures, IEnumerator, and IQueryable.
Boxing and Unboxing
In C#, we can treat any value type (e.g., int
, char
, etc.) as an object (i.e., reference type). Boxing and Unboxing enable the C# unified view of the type system.
- Boxing is converting a value type to an
object
type or any interface type implemented by this value type. - Unboxing is converting the
object
back to a value type.
Extension Methods
Extension methods allow us to add additional methods to existing types (value types, reference types, interfaces) without creating a new derived type, recompiling, or modifying the original type.
So, let’s assume that we frequently need to count the words of a text (string
class). Then, we could create the following extension method (e.g., see CountWords
in Figure 1) that operates on the string
class. As we can see in the following example (Figure 1):
- An extension method is a static method of a static class.
- The first parameter of the method uses the
this
modifier to specify the type that we would extend with the current method. - To call the extension method, we should add a
using
directive to explicitly import the namespace that contains the extension method. - When calling the extension method, we do not need to specify the first parameter. However, we need to specify other possible parameters (e.g.,
bool separateDashes
). - We call the extension method as if they were instance methods on the type.
- Note: The extension class and method should be visible to the calling code (see access modifiers, e.g., public, private, internal, etc.).
When to use Extension Methods
Extension methods are commonly used when we:
- Don’t control the types to be extended,
- Don’t want to force its implementation via inheritance,
- Want to have clean model classes (e.g., DTOs), etc.
We should be careful when creating extension methods to provide simple logic implementation without the need for dependencies. We should use extension methods to improve readability and maintainability by reducing repeated code (see DRY principle).
Anonymous Functions
In C#, anonymous methods
are functions without a name that can be defined using the delegate
keyword. The anonymous methods are usually assigned to a variable of the delegate type. Anonymous methods are helpful when we want to create inline methods (with or without input arguments).
In the following example, we defined the ShowMesssage
anonymous function to get a string
argument and do not return a value. As we can see, at runtime, we can assign the functionality of this function. In this example, we are just writing the input message to the console.
Lambda Expressions
We can use Lambda Expressions to easily create Anonymous Functions to be passed as arguments or returned as the value of other functions. To create a lambda expression, we use the lambda declaration operator =>
to separate the lambda’s parameter list from its body.
Syntax:
A lambda expression that returns a value is expressed as a Func delegate type. Otherwise, it is expressed as an Action delegate type. In the following example, we can see how the use of lambda expressions simplifies creating the same anonymous function that we created in the previous example.
Lambda expressions are compelling, and as we saw in .NET Nakama (2022, March), they are also beneficial for writing LINQ query expressions.
Closures
In lambda expressions (and anonymous functions), we may need to access a non-local variable for flexibility. However, in general, we can only use a local variable in the code block that it’s declared. So, a non-local variable is declared in a different code block.
The access to a non-local variable from a method or function is called Closure
, which can improve the code’s maintainability when used sparingly. The following example shows that the showMesssage
method has access to the aVariable
value. Notice that the "Initial Value"
of the aVariable
string is not shown.
IEnumerable and IEnumerator
The IEnumerable interface enables the non-generic collection classes (such as ArrayList
) to be enumerated using the foreach
statement. In the case of generic collection classes (such as List<T>
, Dictionary<TKey, TValue>
, etc.), we can use the IEnumerable<T>
interface for the same purpose.
The IEnumerable
interface contains only the GetEnumerator()
method, which returns an IEnumerator
that iterates through a collection.
The IEnumerator provides the ability to iterate through the collection by exposing a Current
property and MoveNext()
and Reset()
methods. We can use Enumerators to read the data in the collection and not modify them.
IEnumerable vs. IQueryable
We can use the IQueryable
and IQueryable<T>
interfaces to query data from query providers (such as databases). For that reason, the IQueryable
interfaces represent a query as an expression tree to be executed for different types of data sources.
The IQueryable interfaces inherit from the related IEnumerable
interfaces so that we can enumerate the query result. We can use both of these interfaces to query data. However, as we can see in the following table, they operate quite differently.
IEnumerable | IQueryable | |
---|---|---|
Namespace | System.Collections | System.Linq Namespace |
Suitable for querying data from: | In-memory collections (such as ArrayList, List |
Out-of-memory sources (such as databases) |
When executing a query on the database server (e.g., executing a “SELECT” for SQL). | The query is executed without filtering (e.g., without the “WHERE” clause in SQL). First, the whole data are retrieved in memory (e.g., in the application server), and then the filtering is performed. | The query is executed with all the filters on the database server, and the final data are retrieved. |
Summary
Understanding LINQ and IEnumerable can be quite challenging because they involve several concepts. We may don’t know these concepts or we may know the concepts but not their names. This is quite common when we are practical learners or learning on the job (i.e., no time to read the theory).
In this article, we covered several must-know concepts related to LINQ and IEnumerable. It’s essential to understand these concepts to quickly and effectively operate in our daily jobs as software engineers.
In our next article, we will learn about the .NET Collections (Generic, Concurrent, Immutable, ReadOnly, etc.), so stay tuned!
If you liked this article (or not), do not hesitate to leave comments, questions, suggestions, complaints, or just say Hi in the section below. Don't be a stranger 😉!
Dont't forget to follow my feed and be a .NET Nakama. Have a nice day 😁.