MissingMethodException: El problema de los Service Pack de .NET

Leo en el blog de Jorge Serrano un post sobre las versiones del framework y no puedo evitar pensar en el tremendo lío que ha montado Microsoft con su política de versiones.

Para empezar, la mezcla de CLR, .NET Framework y C# en un mismo paquete hace que sea complicado seguir qué versión de cada cosa va con qué versión de las otras. Por ejemplo, si instalas Microsoft .NET Framework 3.5, acabas con:

  • Versión de CLR: 2.0
  • Versión de C#: 3.0
  • Versión de .NET Framework: 3.5

Además, como dice Jorge, uno de los objetivos iniciales de .NET era acabar con el DLL hell:

La vitola de Microsoft por el famoso infierno de las dll’s no era muy positivo para el gigante norteamericano, pero Microsoft ha sabido recorrer ese camino del cambio de imagen y adaptarse a la exigencia del mercado, dotando a .NET Framework con el paso de los años, de más y más características sin perder la robustez y fiabilidad de un producto ampliamente usado en el mercado.

Se supone que al poder instalar distintas versiones de un mismo assembly en el GAC esto ya no sería un problema, porque cada aplicación usaría la versión del assembly para la que estaba compilada. Esto, unido a la posibilidad de instalar side-by-side distintas versiones del framework parecía resolver el problema.

Pues no. El problema sigue existiendo por culpa de los Service Pack de cada versión del framework. Si en un equipo está instalado, por ejemplo .NET3.5 y se instala SP1, la instalación se convierte en .NET3.5SP1 y no en dos instalaciones side-by-side. Hasta aquí todo lógico, si no fuera porque el SP1 introduce nuevas APIs.

Lo malo de introducir nuevas APIs en el Service Pack viene porque al compilar se usarán los assemblies del SP y esas nuevas APIs quedarán accesibles “sin darnos cuenta”. En el Visual Studio nuestro proyecto seguirá siendo un proyecto de .NET 3.5, pero en realidad a través del intellisense tendremos disponibles las APIs del SP1.

Si no usamos ninguna de las nuevas APIs, no hay problema, pero si las usamos… sólo lo descubriremos al ejecutar la aplicación en un equipo sin el SP1 instalado, cuando se lance una preciosa MissingMethodException.

Que yo sepa, no hay ninguna forma sencilla de saber si un método/clase/etc. está definido en la versión “base” el framework o en el Service Pack excepto, claro está, leer la documentación, pero ¿no estamos usando un lenguaje de tipado estático? ¿No debería ser posible detectar eso en tiempo de compilación? Si quisiera tener que leer la documentación de cada API que voy a usar, programaría en Javascript :-)

Esto me ha pasado más de una vez y es realmente molesto, especialmente cuando se trata de cambios sutiles que pasan desapercibidos. Por ejemplo, el método WaitHandle.WaitOne (http://msdn.microsoft.com/es-es/library/system.threading.waithandle.waitone(v=VS.90).aspx) tiene 3 sobrecargas en el framework 3.5:

bool WaitOne();
bool WaitOne(int millisecondsTimeout, bool exitContext)
bool WaitOne(TimeSpan timeout, bool exitContext)

Pero el SP1 añade otras dos:

bool WaitOne(int millisecondsTimeout)
bool WaitOne(TimeSpan timeout)

Es muy fácil usar inadvertidamente una de las sobrecargas añadidas en el SP1 y provocar errores que sólo serán visibles en tiempo de ejecución.

No conozco ninguna herramienta para verificar de forma automática la versión de framework requerida realmente para ejecutar una aplicación, por lo que la única salida es tener cuidado, armarse de paciencia y tener preparadas máquinas virtuales con los frameworks instalados con distintos services packs para probar manualmente.