La promessa di LINQ è quella di poter scrivere interrogazioni SQL-like per qualunque oggetto che implementi l'interfaccia IEnumerable<T>, dove T è il tipo degli elementi della "sequenza" da interrogare. Le query possono essere, tra l'altro, eseguite in parte da un DBMS (DLINQ) e in parte in memoria.
Gran parte delle feature di LINQ si basano su alcune estensioni a C# (idem per VB.Net) supportate dalla prossima versione del compilatore (3.0). Di queste feature, due sono già presenti nell'attuale compilatore C# 2: generics e iterators. Generics e iterators sono un'accoppiata formidabile per generare astrazioni.
Grazie a tutto questo è possibile scrivere una classe Sequence<T> che mimi alcune delle funzionalità implementate dagli extension method della omonima classe statica di LINQ:

public class Sequence<T> : IEnumerable<T>
{
  
private IEnumerable<T> sequence = null;

   public Sequence(IEnumerable<T> sequence)
  
{
      this.sequence = sequence;
  
}

   public List<T> ToList()
  
{
     
List<T> list = new List<T>();

      foreach (T item in this)
        
list.Add(item);

      return list;
  
}

   public T[] ToArray()
  
{
     
return ToList().ToArray();
  
}

   // Cast implici da T[] e List<T> a Sequence<T>.
  
public static implicit operator Sequence<T>(T[] sequence)
   {
     
return new Sequence<T>(sequence);
   }

   public static implicit operator Sequence<T>(List<T> sequence)
  
{
     
return new Sequence<T>(sequence);
  
}

   private IEnumerable<T> ConcatIterator(Sequence<T> sequence)
  
{
     
foreach (T item in this.sequence)
        
yield return item;

      foreach (T item in sequence)
        
yield return item;
  
}

   public Sequence<T> Concat(Sequence<T> sequence)
   
{
     
return new Sequence<T>(ConcatIterator(sequence));
  
}

   private IEnumerable<T> WhereIterator(Predicate<T> condition)
  
{
     
foreach (T item in sequence)
     
{
        
if (condition(item))
           
yield return item;
     
}
  
}

   public Sequence<T> Where(Predicate<T> condition)
  
{
     
return new Sequence<T>(WhereIterator(condition));
  
}

   public IEnumerator<T> GetEnumerator()
  
{
     
foreach (T item in sequence)
        
yield return item;
  
}

   System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  
{
      
foreach (object item in sequence)
        
yield return item;
  
}
}

Vediamo alcuni esempi di codice che sfruttano la precedente classe:

Sequence<int> sq = new int[] { 1, 2, 3 }; // Cast implicito.
sq = sq.Concat(new int[] { 4, 5, 6 });
foreach (int item in sq.Where(delegate (int i) { return i > 2; }))
  
Console.WriteLine(item);

Stampa:

3 4 5 6

Il seguente esempio trasforma invece una SqlDataReader, risultato di una query sul db Northwind, andando poi a filtrare i record in memoria:

static IEnumerable<string> GetEmployees()
{
  
using (SqlConnection cn = new SqlConnection("SERVER=(local);DATABASE=Northwind;USER ID=sa;PASSWORD=;"))
  
{
     
cn.Open();
     
SqlCommand cmd = new SqlCommand("select FirstName from Employees", cn);
      
using (SqlDataReader rd = cmd.ExecuteReader())
     
{
        
while (rd.Read())
           
yield return rd["FirstName"].ToString();
     
}
  
}
}

Sequence<string> s = new Sequence<string>(GetEmployees());
foreach (string name in s.Where(delegate(string item) { return item.StartsWith("A"); }))
   Console
.WriteLine(name);

Stamperebbe a console i soli impiegati il cui nome inizia per A.