Nota
O acceso a esta páxina require autorización. Pode tentar iniciar sesión ou modificar os directorios.
O acceso a esta páxina require autorización. Pode tentar modificar os directorios.
En este tutorial, obtendrá información sobre qué significa el control de versiones en .NET. También obtendrá información sobre los factores que deben tenerse en cuenta para controlar las versiones de su biblioteca así como para actualizar a una versión nueva de esta.
Versión de lenguaje
El compilador de C# forma parte del SDK de .NET. De forma predeterminada, el compilador elige la versión del lenguaje C# que coincide con el TFM elegido para su proyecto. Si la versión del SDK es mayor que el marco elegido, el compilador podría usar una versión de lenguaje mayor. Puede cambiar el valor predeterminado si establece el elemento LangVersion en su proyecto. Puede obtener información sobre cómo hacerlo en nuestro artículo sobre opciones del compilador.
Advertencia
No se recomienda establecer el elemento LangVersion como latest. La configuración latest significa que el compilador instalado usa su versión más reciente. Esto puede cambiar de equipo a equipo, lo que hace que las compilaciones no sean confiables. Además, habilita las características de lenguaje que pueden requerir tiempo de ejecución o características de biblioteca que no se incluyen en el SDK actual.
Creación de bibliotecas
Como desarrollador que ha creado bibliotecas de .NET para uso público, probablemente se ha encontrado en situaciones en las que tiene que implementar nuevas actualizaciones. Cómo realizar este proceso es muy importante, ya que necesita asegurarse de que existe una transición sin problemas del código existente a la versión nueva de su biblioteca. Aquí se muestran algunos aspectos para tener en cuenta a la hora de crear una versión nueva:
Control de versiones semántico
Control de versiones semántico (SemVer para abreviar) es una convención de nomenclatura que se aplica a las versiones de su biblioteca para indicar eventos importantes específicos. De manera ideal, la información de la versión que proporciona a la biblioteca debe ayudar a los desarrolladores a determinar la compatibilidad con sus proyectos que usan versiones anteriores de la misma biblioteca.
El enfoque más sencillo de SemVer es el formato de 3 componentes MAJOR.MINOR.PATCH, donde:
-
MAJORse incrementa cuando realiza cambios de API incompatibles -
MINORse incrementa cuando agrega funciones de manera compatible con versiones anteriores -
PATCHse incrementa cuando realiza correcciones de errores compatibles con versiones anteriores
Descripción de los incrementos de versión con ejemplos
Para ayudar a aclarar cuándo incrementar cada número de versión, estos son ejemplos concretos:
Actualizaciones de la versión principal (cambios de API incompatibles)
Estos cambios requieren que los usuarios modifiquen su código para trabajar con la nueva versión:
Quitar un método público o propiedad:
// Version 1.0.0 public class Calculator { public int Add(int a, int b) => a + b; public int Subtract(int a, int b) => a - b; // This method exists } // Version 2.0.0 - MAJOR increment required public class Calculator { public int Add(int a, int b) => a + b; // Subtract method removed - breaking change! }Cambio de firmas de método:
// Version 1.0.0 public void SaveFile(string filename) { } // Version 2.0.0 - MAJOR increment required public void SaveFile(string filename, bool overwrite) { } // Added required parameterCambiar el comportamiento de los métodos existentes de maneras que interrumpen las expectativas:
// Version 1.0.0 - returns null when file not found public string ReadFile(string path) => File.Exists(path) ? File.ReadAllText(path) : null; // Version 2.0.0 - MAJOR increment required public string ReadFile(string path) => File.ReadAllText(path); // Now throws exception when file not found
Incrementos de versión SECUNDARIA (funcionalidad compatible con versiones anteriores)
Estos cambios agregan nuevas características sin interrumpir el código existente:
Añadir nuevos métodos o propiedades públicas:
// Version 1.0.0 public class Calculator { public int Add(int a, int b) => a + b; } // Version 1.1.0 - MINOR increment public class Calculator { public int Add(int a, int b) => a + b; public int Multiply(int a, int b) => a * b; // New method added }Adición de nuevas sobrecargas:
// Version 1.0.0 public void Log(string message) { } // Version 1.1.0 - MINOR increment public void Log(string message) { } // Original method unchanged public void Log(string message, LogLevel level) { } // New overload addedAdición de parámetros opcionales a los métodos existentes:
// Version 1.0.0 public void SaveFile(string filename) { } // Version 1.1.0 - MINOR increment public void SaveFile(string filename, bool overwrite = false) { } // Optional parameterNota
Se trata de un cambio compatible con el origen, pero un cambio que rompe con la compatibilidad binaria. Los usuarios de esta biblioteca deben volver a compilarla para que funcione correctamente. Muchas bibliotecas considerarían esto solo en los cambios de versión principales , no en los cambios de versión secundaria .
Incrementos en la versión PATCH (correcciones de errores compatibles con versiones anteriores)
Estos cambios corrigen problemas sin agregar nuevas características ni interrumpir la funcionalidad existente:
Corrección de un error en la implementación de un método existente:
// Version 1.0.0 - has a bug public int Divide(int a, int b) { return a / b; // Bug: doesn't handle division by zero } // Version 1.0.1 - PATCH increment public int Divide(int a, int b) { if (b == 0) throw new ArgumentException("Cannot divide by zero"); return a / b; // Bug fixed, behavior improved but API unchanged }Mejoras de rendimiento que no cambian la API:
// Version 1.0.0 public List<int> SortNumbers(List<int> numbers) { return numbers.OrderBy(x => x).ToList(); // Slower implementation } // Version 1.0.1 - PATCH increment public List<int> SortNumbers(List<int> numbers) { var result = new List<int>(numbers); result.Sort(); // Faster implementation, same API return result; }
El principio clave es: si el código existente puede usar la nueva versión sin cambios, es una actualización MINOR o PATCH. Si es necesario modificar el código existente para que funcione con la nueva versión, se trata de una actualización principal.
También existen maneras de especificar otros escenarios, por ejemplo, versiones preliminares, al aplicar información de la versión a su biblioteca .NET.
Compatibilidad con versiones anteriores
A medida que presente versiones nuevas de su biblioteca, la compatibilidad con las versiones anteriores será probablemente una de sus mayores preocupaciones. Una versión nueva de su biblioteca es compatible en su origen con una versión anterior si el código que depende de la versión anterior, puede, cuando se vuelve a compilar, trabajar con la versión nueva. Una versión nueva de su biblioteca tiene compatibilidad binaria si una aplicación que dependía de la versión anterior, puede, sin que se vuelva a compilar, trabajar con la versión nueva.
Aquí se muestran algunos aspectos a tener en cuenta al intentar mantener la compatibilidad con versiones anteriores de su biblioteca:
- Métodos virtuales: cuando hace que un método virtual sea no virtual en la versión nueva, significa que los proyectos que reemplacen ese método tendrán que actualizarse. Esto es un cambio brusco enorme y se desaconseja totalmente.
- Firmas de método: cuando actualizar un comportamiento del método requiere que también se cambie su firma, en su lugar se debe crear una sobrecarga de manera que el código que llama a ese método siga funcionando. Siempre puede manipular la firma del método anterior para llamar a la firma del método nuevo, de manera que la implementación siga siendo coherente.
- Atributo obsoleto: puede usar este atributo en el código para especificar clases o miembros de clases que han quedado obsoletos y que probablemente se quiten en versiones futuras. Esto garantiza que los desarrolladores que usen su biblioteca estén mejor preparados para los cambios bruscos.
- Argumentos de método opcionales: cuando hace que los argumentos de método opcionales anteriores sean obligatorios o cambien su valor predeterminado, se tendrá que actualizar todo el código que no proporcione esos argumentos.
Nota
Hacer que los argumentos obligatorios sean opcionales debe tener un efecto muy pequeño, especialmente si no cambia el comportamiento del método.
Cuánto más facilite la actualización a la nueva versión de la biblioteca a sus usuarios, más rápidamente la actualizarán.
Archivo de configuración de aplicación
Como desarrollador de .NET, existe una posibilidad muy alta de que haya encontrado el archivo app.config en la mayoría de tipos de proyecto.
Este sencillo archivo de configuración puede hacer mucho por mejorar la implementación de las actualizaciones nuevas. Generalmente, debe diseñar sus bibliotecas de tal manera que la información que es probable que cambie regularmente se almacene en el archivo app.config, de esta manera, cuando dicha información se actualice, el archivo de configuración de las versiones anteriores solo necesita reemplazarse por el nuevo sin necesidad de volver a compilar la biblioteca.
Consumo de bibliotecas
Como desarrollador que consume bibliotecas .NET creadas por otros desarrolladores, es probable que sea consciente de que una nueva versión de una biblioteca puede que no sea completamente compatible con su proyecto y, a menudo, puede que tenga que actualizar su código para trabajar con esos cambios.
Por suerte, C# y el ecosistema de .NET incluyen características y técnicas que nos permiten actualizar fácilmente nuestra aplicación para que funcione con las versiones nuevas de bibliotecas que pueden presentar cambios bruscos.
Redirección de enlace de ensamblados
Puede usar el archivo app.config para actualizar la versión de una biblioteca que use su aplicación. Al agregar lo que se denomina una redirección de enlace, se puede usar la nueva versión de la biblioteca sin tener que volver a compilar la aplicación. En el siguiente ejemplo se muestra cómo actualizaría el archivo app.config de la aplicación para usar la versión de revisión 1.0.1 de ReferencedLibrary, en lugar de la versión 1.0.0 con la que se ha compilado originalmente.
<dependentAssembly>
<assemblyIdentity name="ReferencedLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
<bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>
Nota
Este enfoque solo funcionará si la versión nueva de ReferencedLibrary tiene compatibilidad binaria con su aplicación.
Vea la sección anterior Compatibilidad con versiones anteriores para ver los cambios que debe tener en cuenta al determinar la compatibilidad.
nuevo
Use el modificador new para ocultar los miembros heredados de una clase base. Esta es una manera en la que las clases derivadas pueden responder a las actualizaciones en clases base.
Considere el ejemplo siguiente:
public class BaseClass
{
public void MyMethod()
{
Console.WriteLine("A base method");
}
}
public class DerivedClass : BaseClass
{
public new void MyMethod()
{
Console.WriteLine("A derived method");
}
}
public static void Main()
{
BaseClass b = new BaseClass();
DerivedClass d = new DerivedClass();
b.MyMethod();
d.MyMethod();
}
Resultados
A base method
A derived method
En el ejemplo anterior puede ver cómo DerivedClass oculta el método MyMethod presente en BaseClass.
Esto significa que cuando una clase base en la versión nueva de una biblioteca agrega un miembro que ya existe en su clase derivada, simplemente puede usar el modificador new en su miembro de clase derivada para ocultar el miembro de clase base.
Cuando no se especifica ningún modificador new, una clase derivada ocultará de manera predeterminada los miembros en conflicto de una clase base, aunque se generará una advertencia del compilador, el código se compilará. Esto significa que agregar simplemente miembros nuevos a una clase existente, hace que la versión nueva de su biblioteca tenga compatibilidad binaria y de origen con el código que depende de ella.
override
El modificador override significa que una implementación derivada extiende la implementación de un miembro de clase base en lugar de ocultarlo. El miembro de clase base necesita que se le aplique el modificador virtual.
public class MyBaseClass
{
public virtual string MethodOne()
{
return "Method One";
}
}
public class MyDerivedClass : MyBaseClass
{
public override string MethodOne()
{
return "Derived Method One";
}
}
public static void Main()
{
MyBaseClass b = new MyBaseClass();
MyDerivedClass d = new MyDerivedClass();
Console.WriteLine($"Base Method One: {b.MethodOne()}");
Console.WriteLine($"Derived Method One: {d.MethodOne()}");
}
Resultados
Base Method One: Method One
Derived Method One: Derived Method One
El modificador override se evalúa en tiempo de compilación y el compilador producirá un error si no encuentra un miembro virtual que reemplazar.
Su conocimiento de las técnicas que se han tratado y su comprensión de las situaciones en las cuales usarlas, contribuirá en gran medida a facilitar la transición entre las versiones de una biblioteca.