Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
A lo largo de su historial, .NET ha intentado mantener un alto nivel de compatibilidad de la versión a la versión y entre implementaciones de .NET. Aunque .NET 5 (y .NET Core) y versiones posteriores se pueden considerar como una nueva tecnología en comparación con .NET Framework, dos factores principales limitan la capacidad de esta implementación de .NET para diferir de .NET Framework:
- Un gran número de desarrolladores han desarrollado originalmente o continúan desarrollando aplicaciones de .NET Framework. Esperan un comportamiento coherente en las implementaciones de .NET.
- Los proyectos de biblioteca de .NET Standard permiten a los desarrolladores crear bibliotecas destinadas a API comunes compartidas por .NET Framework y .NET 5 (y .NET Core) y versiones posteriores. Los desarrolladores esperan que una biblioteca usada en una aplicación .NET se comporte de forma idéntica a la misma biblioteca que se usa en una aplicación de .NET Framework.
Junto con la compatibilidad entre implementaciones de .NET, los desarrolladores esperan un alto nivel de compatibilidad entre versiones de una implementación determinada de .NET. En concreto, el código escrito para una versión anterior de .NET Core debe ejecutarse sin problemas en .NET 5 o una versión posterior. De hecho, muchos desarrolladores esperan que las nuevas API que se encuentran en las versiones de .NET recién publicadas también sean compatibles con las versiones preliminares en las que se introdujeron esas API.
En este artículo se describen los cambios que afectan a la compatibilidad y a la forma en que el equipo de .NET evalúa cada tipo de cambio. Comprender cómo el equipo de .NET aborda los posibles cambios importantes es especialmente útil para los desarrolladores que abren solicitudes de incorporación de cambios que modifican el comportamiento de las API de .NET existentes.
En las secciones siguientes se describen las categorías de cambios realizados en las API de .NET y su impacto en la compatibilidad de aplicaciones. Los cambios se permiten (✔️), no se permiten (❌) o requieren juicio y una evaluación de cómo de predecible, obvio y coherente fue el comportamiento anterior (❓).
Nota:
- Además de servir como guía sobre cómo se evalúan los cambios en las bibliotecas de .NET, los desarrolladores de bibliotecas también pueden usar estos criterios para evaluar los cambios en sus bibliotecas destinadas a varias implementaciones y versiones de .NET.
- Para obtener información sobre las categorías de compatibilidad, por ejemplo, compatibilidad con versiones anteriores y posteriores, vea Cómo los cambios de código pueden afectar a la compatibilidad.
Modificaciones en el contrato público
Los cambios en esta categoría modifican el área expuesta pública de un tipo. La mayoría de los cambios de esta categoría no se permiten, ya que infringen la compatibilidad con versiones anteriores (la capacidad de una aplicación desarrollada con una versión anterior de una API para ejecutarse sin volver a compilarla en una versión posterior).
Tipos
✔️ PERMITIDO: quitar una implementación de interfaz de un tipo cuando una interfaz ya está implementada por un tipo base
❓ REQUIERE CRITERIO: Agregar una nueva implementación de interfaz a un tipo
Se trata de un cambio aceptable porque no afecta negativamente a los clientes existentes. Los cambios en el tipo deben funcionar dentro de los límites de los cambios aceptables definidos aquí para que la nueva implementación siga siendo aceptable. Es necesario tener precaución extrema al agregar interfaces que afectan directamente a la capacidad de un diseñador o serializador para generar código o datos que no se pueden consumir de nivel descendente. Un ejemplo es la ISerializable interfaz .
❓ REQUIERE SENTENCIA: Presentación de una nueva clase base
Un tipo se puede introducir en una jerarquía entre dos tipos existentes si no introduce nuevos miembros abstractos ni cambia la semántica o el comportamiento de los tipos existentes. Por ejemplo, en .NET Framework 2.0, la DbConnection clase se convirtió en una nueva clase base para SqlConnection, que anteriormente se había derivado directamente de Component.
✔️ PERMITIDO: Mover un tipo de un ensamblado a otro
El ensamblado anterior debe marcarse con el TypeForwardedToAttribute que apunta al nuevo ensamblado.
✔️ PERMITIDO: Cambiar un tipo struct a un tipo
readonly structNo se permite cambiar un
readonly structtipo a unstructtipo.✔️ ALLOWED: adición de la palabra clave sealed o abstract a un tipo cuando no hay constructores accesibles (públicos o protegidos)
✔️ PERMITIDO: expandir la visibilidad de un tipo
❌ NO PERMITIDO: Cambiar el espacio de nombres o el nombre de un tipo
❌ NO PERMITIDO: Cambio de nombre o eliminación de un tipo público
Esto interrumpe todo el código que utiliza el tipo cuyo nombre se ha cambiado o quitado.
Nota:
En raras ocasiones, .NET puede quitar una API pública. Para más información, consulte Eliminación de API en .NET. Para obtener información sobre la Directiva de soporte técnico de .NET, consulte Directiva de soporte técnico de .NET.
❌ NO PERMITIDO: Cambiar el tipo subyacente de una enumeración
Se trata de un cambio importante en tiempo de compilación y de comportamiento, así como de un cambio importante binario que puede hacer que los argumentos de atributos no se puedan analizar.
❌ NO PERMITIDO: Sellado de un tipo que anteriormente no estaba sellado
❌ NO PERMITIDO: Agregar una interfaz al conjunto de tipos base de una interfaz
Si una interfaz implementa una interfaz que previamente no implementaba, todos los tipos que implementaron la versión original de la interfaz se rompen.
❓ REQUIERE SENTENCIA: Quitar una clase del conjunto de clases base o una interfaz del conjunto de interfaces implementadas
Hay una excepción a la regla para la eliminación de la interfaz: puede agregar la implementación de una interfaz que deriva de la interfaz eliminada. Por ejemplo, puede eliminar IDisposable si el tipo o la interfaz ahora implementa IComponent, el cual implementa IDisposable.
❌ NO PERMITIDO: Cambio de un
readonly structtipo a un tipo de estructuraSin embargo, se permite el cambio de un
structtipo a unreadonly structtipo.❌ NO PERMITIDO: Cambio de un tipo de estructura a un
ref structtipo y viceversa❌ NO PERMITIDO: Reducción de la visibilidad de un tipo
Sin embargo, se permite aumentar la visibilidad de un tipo.
Miembros
✔️ PERMITIDO: Ampliar la visibilidad de un miembro que no es virtual
✔️ PERMITIDO: Adición de un miembro de tipo abstract a un tipo público que no tiene ningún constructor accesible (público o protegido) o el tipo sealed
Sin embargo, no se permite agregar un miembro abstracto a un tipo que tenga constructores accesibles (públicos o protegidos) y que no sea
sealed.✔️ ALLOWED: restricción de la visibilidad de un miembro protegido cuando el tipo no tiene constructores accesibles (públicos o protegidos) o el tipo está sellado.
✔️ PERMITIDO: Desplazamiento de un miembro a una clase superior en la jerarquía que el tipo del que fue eliminado
✔️ PERMITIDO: Adición o eliminación de una invalidación
La introducción de una invalidación puede hacer que los consumidores anteriores omitan la invalidación cuando llamen a la base.
✔️ ALLOWED: agregar un constructor a una clase, junto con un constructor sin parámetros si la clase anteriormente no tenía constructores.
Sin embargo, no se permite agregar un constructor a una clase que antes no tenía constructores sin agregar el constructor sin parámetros.
✔️ PERMITIDO: Cambio de un valor devuelto
ref readonlya unref(excepto para los métodos o interfaces virtuales)✔️ PERMITIDO: Quitar readonly de un campo, a menos que el tipo estático del campo sea un tipo de valor mutable.
✔️ PERMITIDO: Llamada a un nuevo evento que no se ha definido anteriormente
❓ REQUIERE SENTENCIA: Agregar un nuevo campo de instancia a un tipo
Este cambio afecta a la serialización.
❌ NO PERMITIDO: Cambiar el nombre o quitar un miembro o parámetro público
Esto afecta todo el código que utiliza el miembro renombrado o eliminado, o el parámetro.
Esto incluye el cambio de nombre o eliminación de un captador o establecedor de una propiedad, así como el cambio de nombre o eliminación de los miembros de la enumeración.
❓ REQUIERE CRITERIO: Agregar un miembro a una interfaz
Aunque se trata de un cambio importante en el sentido de que eleva la versión mínima de .NET a .NET Core 3.0 (C# 8.0), que es cuando se introdujeron miembros de interfaz predeterminados (DIMs), se permite agregar un miembro estático, no abstracto y no virtual a una interfaz.
Si proporciona una implementación, agregar un nuevo miembro a una interfaz existente no producirá necesariamente errores de compilación en ensamblados posteriores. Sin embargo, no todos los lenguajes admiten DIMs. Además, en algunos escenarios, el entorno de ejecución no puede decidir qué miembro de interfaz predeterminado invocar. En algunos escenarios, las interfaces son implementadas por tipos de
ref struct. Dado queref structlos tipos no se pueden boxear, no se pueden convertir en tipos de interfaz. Por lo tanto,ref structlos tipos deben proporcionar una implementación implícita para cada miembro de interfaz. No pueden usar la implementación predeterminada proporcionada por la interfaz . Por estos motivos, use el criterio al agregar un miembro a una interfaz existente.❌ NO PERMITIDO: Cambiar el valor de una constante pública o un miembro de enumeración
❌ NO PERMITIDO: Cambiar el tipo de una propiedad, un campo, un parámetro o un valor devuelto
❌ NO PERMITIDO: Agregar, quitar o cambiar el orden de los parámetros
❌ NO PERMITIDO: agregar o quitar la palabra clave in, out o ref de un parámetro
❌ NO PERMITIDO: Cambio de nombre de un parámetro (incluido el cambio de mayúsculas y minúsculas)
Esto se considera disruptivo por dos motivos:
Interrumpe los escenarios de enlace en tiempo de ejecución, como la característica de enlace en tiempo de ejecución en Visual Basic y dinámica en C#.
Interrumpe la compatibilidad con el código fuente cuando los desarrolladores usan argumentos nombrados.
❌ NO PERMITIDO: Cambiar de un valor de retorno
refa un valor de retornoref readonly❌️ NO PERMITIDO: Cambiar de un valor devuelto
ref readonlya un valor devueltorefen un método virtual o una interfaz❌ NO PERMITIDO: Adición o eliminación del tipo abstract de un miembro
❌ NO PERMITIDO: Eliminación de la palabra clave virtual de un miembro
❌ NO PERMITIDO: Adición de la palabra clave virtual a un miembro
Aunque esto a menudo no es un cambio importante porque el compilador de C# tiende a emitir instrucciones de lenguaje intermedio de callvirt (IL) para llamar a métodos que no son virtuales (
callvirtrealiza una comprobación nula, mientras que una llamada normal no), este comportamiento no es invariable por varias razones:- C# no es el único lenguaje que tiene como destino .NET.
- El compilador de C# intenta cada vez más optimizar
callvirta una llamada normal cada vez que el método de destino no es virtual y probablemente no es null (como un método al que se accede a través del operador de propagación ?. NULL).
Convertir un método en virtual significa que el código del consumidor a menudo acabaría llamándolo no virtual.
❌ NO PERMITIDO: Conversión de un miembro virtual en tipo abstract
Un miembro virtual proporciona una implementación de método que se puede invalidar mediante una clase derivada. Un miembro abstracto no proporciona ninguna implementación y se debe invalidar.
❌ NO PERMITIDO: Agregar la palabra clave sealed a un miembro de interfaz
Agregar
sealeda un miembro de interfaz predeterminado hará que este no sea virtual, impidiendo que se invoque la implementación de un tipo derivado para ese miembro.❌ NO PERMITIDO: agregar un miembro abstracto a un tipo público que tenga constructores accesibles (públicos o protegidos) y que no esté sellado
❌ NO PERMITIDO: Adición o eliminación de la palabra clave static de un miembro
❌ NO PERMITIDO: agregar una sobrecarga que impida una sobrecarga existente y defina un comportamiento diferente.
Esto interrumpe a los clientes existentes que se enlazaron a la sobrecarga anterior. Por ejemplo, si una clase tiene una versión única de un método que acepta un UInt32, un consumidor existente se enlazará correctamente a esa sobrecarga al pasar un valor Int32. Sin embargo, si agrega una sobrecarga que acepte un Int32, al volver a compilar o utilizar la característica de enlace en tiempo de ejecución, el compilador se enlaza ahora a la nueva sobrecarga. Si se producen diferentes comportamientos, se trata de un cambio disruptivo.
❌ NO PERMITIDO: agregar un constructor a una clase que anteriormente no tenía ningún constructor sin agregar el constructor sin parámetros
❌️ NO PERMITIDO: Agregar readonly a un campo
❌ NO PERMITIDO: Reducción de la visibilidad de un miembro
Esto incluye reducir la visibilidad de un miembro protegido cuando existen constructores accesibles (
publicoprotected) y el tipo no está sellado. Si no es así, se permite reducir la visibilidad de un miembro protegido.Se permite aumentar la visibilidad de un miembro.
❌ NO PERMITIDO: Cambiar el tipo de un miembro
No se puede modificar el valor devuelto de un método o el tipo de una propiedad o campo. Por ejemplo, la firma de un método que devuelve un Object no se puede cambiar para devolver un String, o viceversa.
❌ NO PERMITIDO: Agregar un campo de instancia a una estructura que no tiene campos no públicos
Si un struct solo tiene campos públicos o no tiene campos en absoluto, los autores de llamadas pueden declarar variables locales de ese tipo de estructura sin llamar al constructor de la estructura o inicializar primero el local en
default(T), siempre y cuando todos los campos públicos se establezcan en la estructura antes de su primer uso. Agregar cualquier nuevo campo (público o no público) a tal estructura representa un cambio fundamental en la fuente para los usuarios, ya que el compilador requerirá que se inicialicen los campos adicionales ahora.Además, agregar cualquier campo nuevo (público o no público) a una estructura sin campos o solo con campos públicos es un cambio importante binario en los autores de llamadas que han aplicado
[SkipLocalsInit]a su código. Dado que el compilador no era consciente de estos campos en tiempo de compilación, podía emitir IL que no inicializa completamente la estructura, lo que provocaba que la estructura se creara a partir de datos de pila no inicializados.Si un struct tiene campos no públicos, el compilador ya aplica la inicialización a través del constructor o
default(T)y la adición de nuevos campos de instancia no es un cambio importante.❌ NO PERMITIDO: Desencadenamiento de un evento existente nunca antes desencadenado
Cambios de comportamiento
Ensamblados
✔️ PERMITIDO: Hacer que un ensamblaje sea portátil cuando todavía se admiten las mismas plataformas
❌ NO PERMITIDO: Cambiar el nombre de un ensamblado
❌ NO PERMITIDO: Cambio de la clave pública de un ensamblado
Propiedades, campos, parámetros y valores devueltos
✔️ ALLOWED: cambio del valor de una propiedad, un campo, un valor devuelto o un parámetro out a un tipo más derivado
Por ejemplo, un método que devuelve un tipo de Object puede devolver una String instancia. (Sin embargo, la firma del método no puede cambiar).
✔️ PERMITIDO: Aumento del intervalo de valores aceptados para una propiedad o parámetro si el miembro no es virtual
Aunque el rango de valores que se pueden pasar al método o que el miembro puede devolver puede expandirse, el tipo de parámetro o miembro no puede cambiar. Por ejemplo, mientras que los valores pasados a un método pueden expandirse de 0 a 124 a 0-255, el tipo de parámetro no puede cambiar de Byte a Int32.
❌ NO PERMITIDO: aumentar el intervalo de valores aceptados para una propiedad o parámetro si el miembro es virtual
Este cambio interrumpe los miembros invalidados existentes, que no funcionarán correctamente para la gama extendida de valores.
❌ NO PERMITIDO: Disminuir el intervalo de valores aceptados para una propiedad o parámetro
❌ NO PERMITIDO: aumentar el intervalo de valores devueltos para una propiedad, un campo, un valor devuelto o un parámetro out
❌ PROHIBIDO: cambiar los valores devueltos para una propiedad, un campo, el valor devuelto de un método o un parámetro out
❌ NO PERMITIDO: Cambio del valor predeterminado de una propiedad, un campo o un parámetro
Cambiar o quitar un valor predeterminado de parámetro no es una interrupción binaria. Eliminar el valor predeterminado de un parámetro es una ruptura del código fuente, y cambiar el valor predeterminado de un parámetro podría dar lugar a una alteración del comportamiento después de la recompilación.
Por este motivo, quitar los valores predeterminados del parámetro es aceptable en el caso específico de "mover" esos valores predeterminados a una nueva sobrecarga de método para eliminar la ambigüedad. Por ejemplo, considere un método
MyMethod(int a = 1)existente. Si introduce una sobrecarga deMyMethodcon dos parámetros opcionalesayb, puede conservar la compatibilidad moviendo el valor predeterminado deaa la nueva sobrecarga. Ahora las dos sobrecargas sonMyMethod(int a)yMyMethod(int a = 1, int b = 2). Este patrón permite aMyMethod()compilar.❌ NO PERMITIDO: Cambio de la precisión de un valor devuelto numérico
❓ REQUIERE EVALUACIÓN: Un cambio en el análisis de la entrada y el inicio de nuevas excepciones (incluso si el comportamiento de análisis no está especificado en la documentación)
Excepciones
✔️ PERMITIDO: lanzar una excepción más derivada que una excepción existente
Dado que la nueva excepción es una subclase de una excepción existente, el código de control de excepciones anterior continúa controlando la excepción. Por ejemplo, en .NET Framework 4, los métodos de creación y recuperación de la referencia cultural comenzaron a iniciar un CultureNotFoundException en lugar de un ArgumentException si no se podía encontrar la referencia cultural. Dado que CultureNotFoundException se deriva de ArgumentException, se trata de un cambio aceptable.
✔️ PERMITIDO: Inicio de una excepción más específica que NotSupportedException, NotImplementedException, NullReferenceException
✔️ PERMITIDO: Inicio de una excepción que se considera irrecuperable
Las excepciones irrecuperables no deben capturarse, sino que deben tratarse por un controlador general de alto nivel. Por lo tanto, no se espera que los usuarios tengan código que capture estas excepciones explícitas. Las excepciones irrecuperables son:
✔️ PERMITIDO: Lanzar una nueva excepción en una nueva trayectoria de código
La excepción solo se debe aplicar a una nueva ruta de acceso de código que se ejecuta con nuevos valores de parámetro o estado y que no se puede ejecutar mediante código existente que tenga como destino la versión anterior.
✔️ ALLOWED: eliminación de una excepción para habilitar un comportamiento más sólido o nuevos escenarios
Por ejemplo, un método
Divideque anteriormente solo trataba valores positivos e iniciaba un ArgumentOutOfRangeException de lo contrario, puede cambiarse para admitir tanto valores negativos como positivos sin iniciar una excepción.✔️ ALLOWED: Cambio del texto de un mensaje de error
Los desarrolladores no deben confiar en el texto de los mensajes de error, que también cambian en función de la referencia cultural del usuario.
❌ NO PERMITIDO: Lanzar una excepción en cualquier otro caso no enumerado anteriormente
❌ NO PERMITIDO: Quitar una excepción en cualquier otro caso no enumerado anteriormente
Atributos
✔️ PERMITIDO: Cambiar el valor de un atributo que no es observable
❌ NO PERMITIDO: cambio del valor de un atributo que es observable
❓ REQUIERE SENTENCIA: Quitar un atributo
En la mayoría de los casos, quitar un atributo (como NonSerializedAttribute) es un cambio importante.
Compatibilidad con plataformas
✔️ PERMITIDO: Admisión de una operación en una plataforma que antes no era posible
❌ NO PERMITIDO: No admitir o requerir ahora un Service Pack específico para una operación que estaba admitida en una plataforma
Cambios de implementación internos
❓ REQUIERE EVALUACIÓN: Cambio del área expuesta de un tipo interno
Estos cambios se permiten generalmente, aunque interrumpen la reflexión privada. En algunos casos, cuando las bibliotecas populares de terceros o un gran número de desarrolladores dependen de las API internas, es posible que no se permitan estos cambios.
❓ REQUIERE EVALUACIÓN: Cambio de la implementación interna de un miembro
Estos cambios suelen permitirse, aunque interrumpen la reflexión privada. En algunos casos, cuando el código del cliente depende con frecuencia de la reflexión privada o de dónde el cambio introduce efectos secundarios no deseados, es posible que estos cambios no se permitan.
✔️ PERMITIDO: Mejora del rendimiento de una operación
La capacidad de modificar el rendimiento de una operación es esencial, pero estos cambios pueden interrumpir el código que depende de la velocidad actual de una operación. Esto es especialmente cierto en el código que depende del tiempo de las operaciones asincrónicas. El cambio de rendimiento no debe tener ningún efecto en otro comportamiento de la API en cuestión; De lo contrario, el cambio se interrumpirá.
✔️ PERMITIDO: Indirectamente (y a menudo adversamente) cambiando el rendimiento de una operación
Si el cambio en cuestión no está clasificado como importante por algún otro motivo, esto es aceptable. A menudo, es necesario realizar acciones que incluyan operaciones adicionales o que agreguen nuevas funcionalidades. Esto casi siempre afectará al rendimiento, pero puede ser esencial para que la API en cuestión funcione según lo previsto.
❌ NO PERMITIDO: cambio de una API sincrónica a asincrónica (y viceversa)
Cambios en el código
✔️ PERMITIDO: Se permite la adición de parámetros a un parámetro
❌ NO PERMITIDO: Cambio de una estructura a una clase y viceversa
❌ NO PERMITIDO: Adición de la instrucción checked a un bloque de código
Este cambio puede provocar que el código que previamente se ejecutaba arroje un OverflowException, lo cual no es aceptable.
❌ NO PERMITIDO: Quitar parámetros de un parámetro
❌ NO PERMITIDO: Cambio del orden en el que se desencadenan los eventos
Los desarrolladores pueden esperar razonablemente que los eventos se activen en el mismo orden y el código de desarrollador con frecuencia depende del orden en el que se desencadenan los eventos.
❌ NO PERMITIDO: Eliminación del inicio de un evento en una acción determinada
❌ NO PERMITIDO: Cambio del número de veces que se llaman los eventos dados
❌ NO PERMITIDO: Agregar a FlagsAttribute un tipo de enumeración