Vamos a hacer una pequeña pausa dentro de nuestro curso de LinQ, y aprovechando que la nueva
versión de Visual Studio, del Framework y de C#, ya están en la calle, daremos un repaso a las nuevas características que nos trae el lenguaje del ‘C Sostenido’.
De primeras podemos decir que los cambios introducidos dentro
de la nueva versión del lenguaje nativo de la plataforma .Net, no son, ni demasiado bestias, ni demasiado amplios, pero
contribuyen y favorecen a una mejor lectura, compresión y organización de
nuestro código.
Para intentar hacer que los posts, sean lo más didácticos posibles, pondré cada uno de los
ejemplos de manera inicial, como se hacían antes (versión 5.0 de C#) y como lo podremos hacer ahora (versión 6.0 de C#) para poder ilustrarnos
con las diferencias.
Entregas anteriores de novedades C# 6.0
Parte 1
Antes de comenzar con la tarea, me gustaría recalcar, que los ejemplos están pensados para la
comprensión, y para no alargar ni ensuciar el código, nos hemos saltado reglas
de arquitectura, validación, etc.
El procedimiento que voy a seguir dentro de este post, es
mostrar una primera clase, realizada completamente en C# 5.0 e ir repasando una primera tanda de novedades para esta
nueva entrega, que a la vez, nos hagan irla transformando, para al final del
todo contemplar su resultado en C# 6.0.
La clase
La clase que he generado para la ocasión, se llamada FilesMto, esta clase, tendría la
misión de realizar un mantenimiento rápido dentro un grupo de ficheros. Vamos a
mostrarla y la comentamos de manera rápida.
public class FilesMto_5
{
private List<string> _ficheros;
public List<string> Ficheros
{
get { return _ficheros; }
}
public Dictionary<string, string> Config { get; set; }
public FilesMto_5(params string[] archivos)
{
_ficheros = archivos.ToList();
CargarConfiguracionPorDefecto();
}
public void CargarConfiguracionPorDefecto()
{
Config = new Dictionary<string, string>();
Config.Add("FormatoFecha", "ddMMyyyymmss");
Config.Add("RutaBackup", @"C:\");
}
public bool ExistenFicheros(string[] files)
{
return ! files.Any(f => ! File.Exists(f));
}
public void HacerBackup(string[] files)
{
files.ToList().ForEach(f => File.Copy(f, CrearDirectorioBackup(f)));
}
public string CrearDirectorioBackup(string archivo)
{
string fichero = Path.GetFileNameWithoutExtension(archivo);
string extension = Path.GetExtension(archivo);
string resultado = Path.Combine(Config["RutaBackup"], string.Format("{0}_{1}{2}", fichero, DateTime.Now.ToString(Config["FormatoFecha"]), extension));
return resultado;
}
}
Lo primero que nos llama la atención al ver la clase, es un ‘_5’. Le he añadido este sufijo,
para diferenciarla, de la que generaremos para la nueva versión, que por
supuesto tendrá el sufijo ‘_6’.
La clase, prácticamente es autoexplicativa, pero en si está
formada por:
2 propiedades:
- Ficheros: Guarda la lista con las
rutas de los ficheros a operar.
- Config: Guarda la configuración
con la que tratar dichos ficheros.
Un constructor
y 3 métodos con nombre
que definen bastante bien su función en el código.
Volver a insistir que muchas partes del código podrían
haberse mejorado, pero que han sido elegidas así, por su mayor nivel
instructivo.
Using Static
En esta nueva versión, se ha añadido la posibilidad de
realizar en la parte destinada a la importación de namespaces, la facultad de hacer using de clases añadiendo la palabra reservada static. Con ello podremos concebir una importación de
los métodos estáticos de la clase.
Voy a intentar explicarme un poquito mejor, poniendo un
ejemplo algo más clarificador, para cada una de ellas:
En este ejemplo, utilizamos las 2 clases estáticas más
famosas del framework, que no son
otras que System.Console y System.Math:
Esta sería una clase de prueba que realizaría una impresión
en pantalla del valor absoluto de un número entero:
using System;
namespace NovedadesCSharp6_1
{
public static class UsingStatic5
{
public static void ImprimirAbsoluto()
{
int valor = -12;
int absValor = Math.Abs(valor);
Console.WriteLine(absValor);
}
}
}
Para ver las
diferencias y la reducción de código que se realiza en esta nueva versión, nada
mejor que ver su actualización para 6.0:
using System;
using static System.Console;
using static System.Math;
namespace NovedadesCSharp6_1
{
public class UsingStatic6
{
public static void ImprimirAbsoluto()
{
int valor = -12;
int absValor = Abs(valor);
WriteLine(absValor);
}
}
}
Como podemos distinguir, hemos eliminado la cualificación de
las clases, dentro de las llamadas a los métodos estáticos:
Math .Abs(valor); --> Abs(valor);
Console.WriteLine(absValor) --> WriteLine(absValor);
Como trasladamos todo esto a nuestra clase, pues de la
siguiente manera:
Simplemente con añadir los using static, para las clases System.IO.File y System.IO.Path,
visual studio 2015, ya nos dará pistas, anunciándonos de que hay código que nos
sobra, y que podemos eliminar.
Así luciría la clase, con nuestro primer retoque:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static System.IO.File;
using static System.IO.Path;
namespace FileUtils
{
public class FilesMto_5
{
private List<string> _ficheros;
public List<string> Ficheros
{
get { return _ficheros; }
}
public Dictionary<string, string> Config { get; set; }
public FilesMto_5(params string[] archivos)
{
_ficheros = archivos.ToList();
CargarConfiguracionPorDefecto();
}
public void CargarConfiguracionPorDefecto()
{
Config = new Dictionary<string, string>();
Config.Add("FormatoFecha", "ddMMyyyymmss");
Config.Add("RutaBackup", @"C:\");
}
public bool ExistenFicheros(string[] files)
{
return ! files.Any(f => ! Exists(f));
}
public void HacerBackup(string[] files)
{
files.ToList().ForEach(f => Copy(f, CrearDirectorioBackup(f)));
}
public string CrearDirectorioBackup(string archivo)
{
string fichero = GetFileNameWithoutExtension(archivo);
string extension = GetExtension(archivo);
string resultado = Combine(Config["RutaBackup"], string.Format("{0}_{1}{2}", fichero, DateTime.Now.ToString(Config["FormatoFecha"]), extension));
return resultado;
}
}
}
He añadido unos comentarios con las sentencias originales,
para que se pueda apreciar mejor el cambio.
Auto Property
Initializer y Dictionary Initializer
Estas son dos propiedades
que debería haber explicado de forma separada, pero dado que tienen
cosas en común y que están vinculadas la una con la otra, dentro del update de nuestra clase, las vamos a
explicar de una vez.
Las Auto
Property Initializer, tienen dos peculiaridades. La primera de ellas
es la posibilidad de permitir realizar Propiedades de solo lectura. Esto antes
no era posible, y solo estaba permitido tener campos de solo lectura, mediante la palabra reservada readonly, pero está no estaba
disponible con las propiedades.
Para emularlas teníamos que valernos de algo como así:
namespace NovedadesCSharp6_1
{
public class MiClase_5
{
private int _miPropiedad;
public int MiPropiedad
{
get { return _miPropiedad; }
}
public MiClase_5(int _propiedadInt)
{
_miPropiedad = _propiedadInt;
}
}
}
Creábamos un campo, al que le vinculábamos una propiedad que solo contenía la
llamada al set.
A alguien se le puede pasar por la cabeza que antes también podíamos utilizar las propiedades automáticas, pero para
poder emplearlas, éstas tenían que tener las dos llamadas al get y al set, en caso de faltar alguna de ellas, nuestro visual studio no nos dejaría
compilar el código.
public int MiPropiedad { get; set; }
En C# 6.0, podemos tener una propiedad con solo una llamada
al get contraído, de manera que nuestra
propiedad sea de solo lectura y solo pueda ser modificada desde el constructor
de nuestra clase:
namespace NovedadesCSharp6_1
{
public class MiClase_6
{
public int MiPropiedad { get; }
public MiClase_6(int _propiedadInt)
{
MiPropiedad = _propiedadInt;
}
}
}
La segunda peculiaridad, radica en la posibilidad de inicializar nuestras propiedades
automáticas de forma autónoma, sin tener que hacer uso del constructor de
nuestra clase.
En el pasado, esta era la manera:
namespace NovedadesCSharp6_1
{
public class MiClase_5
{
public int MiPropiedad { get; set; }
public MiClase_5()
{
MiPropiedad = 25;
}
}
}
Como podemos contemplar, hacemos uso del constructor para
inicializar la propiedad MiPropiedad
con el valor 25.
Esa limitación también se ha visto superada en esta nueva
versión y ahora el código se reduciría a esto:
namespace NovedadesCSharp6_1
{
public class MiClase_6
{
public int MiPropiedad { get; set; } = 25;
}
}
Turno para el Dictionary
Initializer, que no es otra cosa que un inicializador automático de Dictionaries. En las versiones
anteriores, teníamos formas de inicializar clases,
arrays, listas, etc de forma automática:
var miClase = new MiClase_5 { MiPropiedad = 25 };
var array = new int[] { 1, 2, 3, 4 };
var lista = new List<int>() { 1, 2, 3, 4 };
Ahora podemos hacer algo muy parecido a lo que hacemos con
las otras colecciones, así:
ictionary<string, object> claveValor = new Dictionary<string, object>() {["PropiedadInt"] = 2,["PropiedadDateTime"] = DateTime.Today };
Muy sencillito.
Vamos a continuar tuneando nuestra clase, para ello nos
vamos a centrar en esta porción de código:
public Dictionary<string, string> Config { get; set; }
public FilesMto_5(params string[] archivos)
{
_ficheros = archivos.ToList();
CargarConfiguracionPorDefecto();
}
public void CargarConfiguracionPorDefecto()
{
Config = new Dictionary<string, string>();
Config.Add("FormatoFecha", "ddMMyyyymmss");
Config.Add("RutaBackup", @"C:\");
}
Este código está comprendido por la propiedad que guarda la configuración y su alimentación
mediante la llamada al método CargarConfiguracionPorDefecto
dentro del constructor.
Aquí nos podemos quitar la llamada al método CargarConfiguraciónPorDefecto del constructor, inicializando
automáticamente la propiedad Config
así:
public Dictionary<string, string> Config { get; set; } = new Dictionary<string, string>() {["FormatoFecha"] = "ddMMyyyymmss",["RutaBackup"] = @"C:\" };
También podemos reducir el campo y la propiedad Ficheros
de solo lectura:
private List<string> _ficheros;
public List<string> Ficheros
{
get { return _ficheros; }
}
Dejándolo así, como hemos visto antes:
public List<string> Ficheros { get; }
Ya se va apreciando la reducción en nuestra clase:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static System.IO.File;
using static System.IO.Path;
namespace FileUtils
{
public class FilesMto_5
{
public List<string> Ficheros { get; }
public Dictionary<string, string> Config { get; set; } = new Dictionary<string, string>() {["FormatoFecha"] = "ddMMyyyymmss",["RutaBackup"] = @"C:\" };
public FilesMto_5(params string[] archivos)
{
Ficheros = archivos.ToList();
}
public bool ExistenFicheros(string[] files)
{
return ! files.Any(f => ! Exists(f));
}
public void HacerBackup(string[] files)
{
files.ToList().ForEach(f => Copy(f, CrearDirectorioBackup(f)));
}
public string CrearDirectorioBackup(string archivo)
{
string fichero = GetFileNameWithoutExtension(archivo);
string extension = GetExtension(archivo);
string resultado = Combine(Config["RutaBackup"], string.Format("{0}_{1}{2}", fichero, DateTime.Now.ToString(Config["FormatoFecha"]), extension));
return resultado;
}
}
}
Nuevo Formateo de Strings
Esta novedad, no va a tener un impacto de reducción de
código demasiado significativo dentro de nuestra clase, pero puede ser una de
las novedades más prácticas de todas las que forman C# 6.0. Bajo mi experiencia, el método estático String.Format, ofrece una serie de
ventajas a la hora de formatear strings
muy considerables, y no me estoy refiriendo solo a versatilidad y opciones, hablo
también de rendimiento, ese rendimiento que a veces nos juega malas pasadas al
operar con strings y con
cualquier clase inmutable. El 98% de
las veces, este método se emplea para concatenar datos, y dada la falta de claridad
en sus sintaxis, si no lo conoces un poquito más a fondo, la mayoría de la
gente suele inclinarse por la utilización del operador más (+).
Vamos a ver un ejemplo de lo que os comento:
string concatenacion1 = "El valor mínimo de un string es " + int.MinValue + " y el máximo es " + int.MaxValue;
string concatenacion2 = string.Format("El valor mínimo de un string es {0} y el máximo es {1}", int.MinValue, int.MaxValue);
La primera línea normalmente es mucho más sencilla de
seguir, que la segunda.
Pues hecha esta pequeña introducción, vamos a ver la mejora,
que no es otra que la ‘fusión’
de ambas. Simplemente con colocar el carácter de dólar ($), precediendo a las dobles comillas del string, podemos introducir variables
directamente entre llaves, sin tener que utilizar el operador +, y sin tener que emplear la
llamada al string.Format, del
mismo modo que realizamos para las cadenas que contienen directorios con una @ y así no tener que salvar todas
las ‘\’:
string concatenacion3 = $"El valor mínimo de un string es {int.MinValue} y el máximo es {int.MaxValue}";
Con esta mejora, solo podremos reducir un poquito una de las
líneas del código de nuestra clase. Es esta:
string resultado = Combine(Config["RutaBackup"], string.Format("{0}_{1}{2}", fichero, DateTime.Now.ToString(Config["FormatoFecha"]), extension));
Así se quedaría:
string resultado = Combine(Config["RutaBackup"], $"{fichero}_{DateTime.Now.ToString(Config["FormatoFecha"])}{extension}");
Expression Bodied Methods
Esta nueva mejora, ataca directamente a la reducción de
líneas de código, y permite el poder definir métodos de una clase con la
sintaxis de una Expressión Lambda. Mi recomendación para su empleo, es no
utilizarlo nunca para funciones que tengan más de una línea de código, ya que
al implicar la escritura de las llaves, su función se pierde por completo.
En las versiones anteriores hacíamos esto:
public static void DiHola()
{
Console.WriteLine("Holaaa !!!");
}
Y ahora lo dejaríamos en una sola línea, así:
public static void DiHolaEnUnaLinea() => Console.WriteLine("Holaaa !!!");
Puntualizar una cosa, cuando nuestro método tiene
parámetros, es obligatorio poner el tipo dentro de los paréntesis de la Lambda,
ya que al compilador le es imposible inferir el tipo:
public static void DiAlgoEnUnaLinea(string algo) => Console.WriteLine(algo);
Y aquí la simplificación de código en nuestra clase:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static System.IO.Path;
using static System.IO.File;
namespace FileUtils
{
public class FilesMto_5
{
public List<string> Ficheros { get; }
public Dictionary<string, string> Config { get; set; } = new Dictionary<string, string>() {["FormatoFecha"] = "ddMMyyyymmss",["RutaBackup"] = @"C:\" };
public FilesMto_6(params string[] archivos)
{
Ficheros = archivos.ToList();
}
public void CargarConfiguracionPorDefecto() => new Dictionary<string, string>() {["FormatoFecha"] = "dd/MM/yyyy",["RutaBackup"] = @"C:\Backups" };
public bool ExistenFicheros(IEnumerable<string> files) => ! files.Any(f => !Exists(f));
public void HacerBackup(IEnumerable<string> files) => files.ToList().ForEach(f => Copy(f, CrearDirectorioBackup(f)));
public string CrearDirectorioBackup(string archivo)
{
string fichero = GetFileNameWithoutExtension(archivo);
string extension = GetExtension(archivo);
string resultado = Combine(Config["RutaBackup"], $"{fichero}_{DateTime.Now.ToString(Config["FormatoFecha"])}{extension}");
return resultado;
}
}
}
He dejado comentado los métodos modificados para una mejor
comprensión
Resultado
Pues nada aquí el resultado, de la primera (antigua) vs la última
(nueva), para que podamos ver mejor la diferencia.
Antigua:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FileUtils
{
public class FilesMto_5
{
private List<string> _ficheros;
public List<string> Ficheros
{
get { return _ficheros; }
}
public Dictionary<string, string> Config { get; set; }
public FilesMto_5(params string[] archivos)
{
_ficheros = archivos.ToList();
CargarConfiguracionPorDefecto();
}
public void CargarConfiguracionPorDefecto()
{
Config = new Dictionary<string, string>();
Config.Add("FormatoFecha", "ddMMyyyymmss");
Config.Add("RutaBackup", @"C:\");
}
public bool ExistenFicheros(string[] files)
{
return !files.Any(f => !File.Exists(f));
}
public void HacerBackup(string[] files)
{
files.ToList().ForEach(f => File.Copy(f, CrearDirectorioBackup(f)));
}
public string CrearDirectorioBackup(string archivo)
{
string fichero = Path.GetFileNameWithoutExtension(archivo);
string extension = Path.GetExtension(archivo);
string resultado = Path.Combine(Config["RutaBackup"], string.Format("{0}_{1}{2}", fichero, DateTime.Now.ToString(Config["FormatoFecha"]), extension));
return resultado;
}
}
}
Nueva:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static System.IO.Path;
using static System.IO.File;
namespace FileUtils
{
public class FilesMto_6
{
public List<string> Ficheros { get; }
public Dictionary<string, string> Config { get; set; } = new Dictionary<string, string>() {["FormatoFecha"] = "ddMMyyyymmss",["RutaBackup"] = @"C:\" };
public FilesMto_6(params string[] archivos)
{
Ficheros = archivos.ToList();
}
public void CargarConfiguracionPorDefecto() => new Dictionary<string, string>() {["FormatoFecha"] = "dd/MM/yyyy",["RutaBackup"] = @"C:\Backups" };
public bool ExistenFicheros(IEnumerable<string> files) => ! files.Any(f => !Exists(f));
public void HacerBackup(IEnumerable<string> files) => files.ToList().ForEach(f => Copy(f, CrearDirectorioBackup(f)));
public string CrearDirectorioBackup(string archivo)
{
string fichero = GetFileNameWithoutExtension(archivo);
string extension = GetExtension(archivo);
string resultado = Combine(Config["RutaBackup"], $"{fichero}_{DateTime.Now.ToString(Config["FormatoFecha"])}{extension}");
return resultado;
}
}
}
Hasta aquí, la primera entrega de las novedades, en la
siguiente me centraré en las nuevas versatilidades del operador ‘?’, que dan
para mucho, muchísimo.
No hay comentarios :
Publicar un comentario