Date due classi A e B, denotiamo con B <: A il fatto che B è sottoclasse di A.
Una classe generica, diciamo C<T>, si dice covariante rispetto a T se, date due classi A e B tali che B <: A, risulta anche che C<B> <: C<A>.
In modo duale, una classe generica, diciamo C<T>, si dice controvariante rispetto a T se, date due classi A e B tali che B <: A, risulta anche che C<A> <: C<B>.

Gli attuali tipi generici C# non contemplano i concetti di covarianza e controvarianza dei type parameter e questa è una limitazione in diversi casi. Consideriamo un esempio:

using System;
using System.Collections.Generic;
using System.Text;

class Control
{ }

class Button : Control
{ }

class Program
{
  
static void RenderControls(IEnumerable<Control> controls)
  
{
     
foreach (Control control in controls)
        
// ...
        
;
  
}

   static IEnumerable<Button> GetButtons()
  
{
     
yield return new Button();
     
yield return new Button();
  
}

   static void Main(string[] args)
  
{
     
RenderControls(GetButtons()); // Argument '1': cannot convert from  
                                    // 'System.Collections.Generic.IEnumerable<Button>' to
                                    // 'System.Collections.Generic.IEnumerable<Control>'

  
}
}

Nell'esempio è chiaro che un metodo (RenderControls) in grado di enumerare una sequenza di Control è anche in grado di enumerare una sequenza di Button.
L'argomento è noto da tempo e pone diversi problemi teorici a cui sono state date nel tempo diverse soluzioni. Le diverse proposte tentano chiaramente di introdurre covarianza e controvarianza dei type parameter cercando di mantenere "sound" il type system.
Per chi fosse interessato all'argomento consiglio un ottimo articolo di un esperto italiano (!) dell'argomento: Mirko Viroli.
http://www.sato.kuis.kyoto-u.ac.jp/~igarashi/papers/pdf/variance.TOPLAS.pdf

Un gruppo di lavoro di Microsoft Cambridge ha recentemente proposto una soluzione applicata a C#: http://research.microsoft.com/~akenn/generics/ECOOP06.pdf