Introduction
In C#, both IEnumerable and IQueryable are fundamental interfaces for working with collections of data. While they share a common ancestor and similar names, they serve distinct purposes and are optimized for different scenarios. Understanding their differences is crucial for writing efficient and scalable applications.
IEnumerable: In-Memory Data Traversal
The IEnumerable interface is designed for iterating over collections that reside in memory, such as lists, arrays, or custom collections. It provides a standard way to access elements sequentially without exposing the underlying data structure.
At its core, IEnumerable defines a single method, GetEnumerator(), which returns an enumerator object. This enumerator is responsible for moving through the collection and providing access to each item.
When you use LINQ methods like Where or Select on an IEnumerable source, the operations are executed immediately in memory. The methods accept delegates (like Func<T, bool>) that are invoked for each element as it's processed.
Example: Custom In-Memory Collection
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class ProductCatalog : IEnumerable<string>
{
private readonly List<string> _products = new List<string>
{
"Laptop", "Smartphone", "Tablet", "Monitor", "Keyboard"
};
public IEnumerator<string> GetEnumerator()
{
return _products.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class Program
{
public static void Main()
{
var catalog = new ProductCatalog();
// Using LINQ with IEnumerable: operations happen in memory
var electronicItems = catalog.Where(item => item.Contains("phone") || item.Contains("phone"));
Console.WriteLine("Electronic Items (IEnumerable):");
foreach (var item in electronicItems)
{
Console.WriteLine(item);
}
}
}
</string></string></string></string>
IQueryable: Remote Data Querying
The IQueryable interface builds upon IEnumerable by adding support for query composition and translation. It's primarily used when the data source is external, such as a database, and you want to leverage the data source's query capabilities (e.g., SQL).
Unlike IEnumerable, IQueryable methods accept expression trees (like Expression<Func<T, bool>>) instead of delegates. An expression tree represents the code as data, which can then be analyzed and translated by a query provider (e.g., Entity Framework) into a native query language like SQL. This allows for significant performance optimizations, such as filtering data on the database server before it's sent to the application.
Queries on IQueryable sources are typically executed lazily, meaning the actual data retrieval happens only when the result are enumerated (e.g., in a foreach loop or when calling ToEnumerable() or ToList()).
Example: Simulating a Database Query
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
public class DatabaseContext
{
public IQueryable<string> Products => new List<string>
{
"Laptop", "Smartphone", "Tablet", "Monitor", "Keyboard"
}.AsQueryable();
}
public class Program
{
public static void Main()
{
var context = new DatabaseContext();
// Using LINQ with IQueryable: query is translated to SQL-like logic
var electronicItems = context.Products.Where(item => item.Contains("phone"));
Console.WriteLine("Electronic Items (IQueryable - translated to SQL):");
// The actual filtering happens here, potentially on a remote server
foreach (var item in electronicItems)
{
Console.WriteLine(item);
}
}
}
</string></string>
Key Differences
- Data Source:
IEnumerableis for in-memory collections.IQueryableis for remote data sources (databases, web services). - Execution:
IEnumerableexecutes queries immediately in memory.IQueryableuses deferred execution, translating the query for remote execution. - Performance: For small, local data,
IEnumerablecan be faster due to lower overhead. For large datasets or remote sources,IQueryableis superior as it minimizes data transfer and leverages server-side processing. - Method Parameters:
IEnumerableLINQ methods use delegates (Func<T, TResult>).IQueryableLINQ methods use expression trees (Expression<Func<T, TResult>>).