C # y .NET han estado en constante desarrollo activo durante las últimas dos décadas; cada año, el lenguaje recibe una serie de nuevas funciones útiles. Discutiremos algunos de nuestros favoritos que creemos que los principiantes deberían conocer.
Tipos de referencia que aceptan valores NULL
C # tenía nulos valor tipos por un tiempo, como " int?
"Que puede contener un int
o el valor de null
, a diferencia de uno tradicional int
que siempre tendría un valor predeterminado de cero. Estos son útiles para muchas cosas, incluidas las clases destinadas a la deserialización JSON, donde no todos los campos pueden estar presentes.
Sin embargo, los tipos de referencia siempre han sido posibles para asignar un archivo null
valor, entonces, ¿cuál es el punto de esta nueva característica de C # 8.0?
Los tipos de referencia que aceptan valores NULL básicamente imponen una distinción entre las variables de referencia que pueden convertirse en nulas y las variables de referencia que no. Es una característica de ruptura que probablemente dejará su base de código con muchas advertencias, por lo que es algo que debe activar manualmente. Una vez encendido, el compilador comienza a distinguir entre:
string?
, que puede ser nulo y mantiene el comportamiento "predeterminado" de versiones anteriores ystring
, cuales No puede ser nulo. Nunca puede ser nulo, porque se debe asignar un valor predeterminado y nunca se puede establecer en nulo.
Esto tiene muchos efectos interesantes, pero el más importante es un enfoque implícito en el correcto null
gestión del valor lingüístico. Por ejemplo, el compilador lo regañará si intenta devolver un valor nulo para una función que devuelve un valor que no acepta valores NULL. También lo regañará si intenta pasar un valor posiblemente nulo a una función que no lo espera.
Si bien esto puede parecer restrictivo, al igual que la escritura estática, en última instancia conduce a un código mejor y más utilizable. Recomendamos encarecidamente habilitarlo para la mayoría de los proyectos y puede leer nuestra guía completa para obtener más información.
Para activarlo, deberá editar el archivo del proyecto. En Visual Studio, haga clic con el botón derecho en el proyecto y haga clic en "Editar archivo de proyecto". Luego enciéndalo con la siguiente directiva:
<Nullable>enable</Nullable>
Si está utilizando el formato de proyecto heredado, es posible que deba anularlo manualmente con una directiva al comienzo de cada archivo:
#nullable enable
RELACIONADO: ¿Cómo funcionan los tipos de referencia que aceptan valores NULL de C #?
Operadores coalescentes nulos y condicionales
En lugar de tener que comprobar if(something == null)
, C # tiene un atajo fantástico con operadores de acceso a miembros con condiciones nulas. Básicamente, en lugar de usar un punto para acceder a algo que podría ser nulo, puede usar un signo de interrogación y un punto, que automáticamente hará la verificación nula.
También puede usarlos para llamar a métodos en objetos nulos o acceder a índices en matrices nulas. Si el objeto termina siendo nulo, simplemente no hace nada y devuelve nulo.
reference?.field reference?.method(); reference?[N]
Tenga en cuenta que este último no evita una IndexOutOfRangeException: simplemente accede al elemento N-ésimo de una lista posiblemente nula.
Sin embargo, es posible que deba trabajar con los valores nulos devueltos por esta expresión y simplificar lo que C # tiene operadores coalescentes nulos. Estos se pueden utilizar para asignar un valor alternativo en caso de que una expresión (cualquier expresión) devuelva un valor nulo. Básicamente, son valores de respaldo. Puede especificarlos con dobles signos de interrogación:
string value = GetValue() ?? "Backup"
También está el ??=
operador, que funciona como ||
ya que no evaluará el valor de respaldo si el primer valor devuelve un resultado correcto.
Tupla
¿Alguna vez ha querido devolver varios valores de un método? Con las tuplas puede hacerlo y el C # moderno ha tenido un gran soporte para lenguajes desde C # 7.0. Simplemente devuelva dos valores encerrados entre paréntesis y separados por comas y podrá acceder a los elementos individuales dentro de ellos.
Aunque no es obligatorio, es una práctica común proporcionar estos nombres, por ejemplo (float X, float Y, float Z)
en lugar de acceder a él según los números de artículo.
También puede usar la deconstrucción de tuplas para descomponer una tupla en múltiples variables de componentes.
Esto es realmente muy útil para constructores simples, donde necesita establecer algunos campos iguales a los argumentos de entrada. El uso de la deconstrucción de tuplas logra esto bastante bien:
Sobrecarga del constructor con: this ()
Los constructores, como cualquier otro método, se pueden sobrecargar con el operador para admitir muchas combinaciones diferentes de parámetros. Sin embargo, como los constructores se utilizan comúnmente para inicializar muchos campos, esto puede conducir a la duplicación de código.
Una solución rápida y sucia sería compartir un método de "inicialización de clase" que es llamado por todos los métodos del constructor sobrecargado, pero si tiene habilitados los tipos de referencia que aceptan valores NULL, obtendrá advertencias de nulabilidad para los campos que no aceptan valores NULL que realmente vienen set, porque el compilador no es lo suficientemente inteligente como para comprender la inicialización en llamadas a funciones impuras.
Pero hay una solución para eso, y es un poco extraño. Viene de herencia del constructor, que es otra gran característica que le permite expandir el constructor de la clase base. Utiliza la misma sintaxis de herencia, dos puntos, seguidos de base (parameters)
. Esto llamará automáticamente al constructor base (antes del nuevo). Tenga en cuenta que aún necesita poner los parámetros del constructor base en la definición del método.
Lo bueno es que no necesitas usar base
; puedes hacer exactamente lo mismo con : this ()
, que llamará a un constructor dentro de la propia clase. Puede usarlo para especificar parámetros adicionales sin copiar el código de inicialización.
Por supuesto, tendrá que declarar los campos opcionales como anulables, ya que el constructor base no los admite. Pero eso es por diseño aquí; en este ejemplo, debe establecer el nombre y apellido de la persona, pero el mensaje de correo electrónico puede serlo o no, lo que lo hace adecuado para un tipo que acepta valores NULL.
Constructores estáticos
Los constructores se usan comúnmente para instanciar clases usando new
palabra clave. En el constructor puede establecer los campos necesarios para inicializar la clase.
Pero, ¿qué pasa con las clases estáticas? Bueno, también pueden usar constructores. De hecho, las clases regulares pueden usar constructores estáticos para establecer sus propiedades estáticas.
Sin embargo, estos no se ejecutan exactamente al inicio. Aunque el ejemplo anterior parece correcto, configurar el startupTime
en un constructor estático, no está garantizado en tiempo de ejecución porque C # y MSIL en el que se ejecuta son un lenguaje compilado Just-In-Time.
La compilación JIT ocurre, bueno, justo a tiempo, exactamente cuando se necesita la clase. Esto significa que la clase se sentará en su esquina de la asamblea, acumulando polvo hasta que se necesite uno de sus campos o métodos. Cuando sea necesario, el tiempo de ejecución de .NET lo repasará, lo compilará y solo entonces llamará al constructor estático.
Sin embargo, el constructor estático todavía se ejecuta primero nada, incluso antes de que se establezcan los campos estáticos y antes de que se pueda hacer referencia a algo. Siguen siendo bastante útiles cuando los necesitas. Alternativamente, puede llamar a un método de inicialización desde la rutina de inicio de la aplicación si necesita hacer algo en orden cronológico.
Parámetros de tipo genérico
Definitivamente se ha encontrado con estos antes, aunque es posible que no haya escrito uno usted mismo. Los parámetros de tipo genérico le permiten escribir funciones independientes del tipo y no importa qué tipo se les pase. El principal ejemplo de esto son las colecciones; una List<string>
está en List<int>
utilizan el mismo código, pero se pasa un parámetro de tipo genérico diferente.
Los genéricos son bastante fáciles de usar por sí solos. Simplemente agregue un nombre para la variable de tipo entre paréntesis en la definición de clase o método. Es una práctica común usar T, o al menos nombres que comienzan con T. Por ejemplo, un diccionario puede tener TKey y TValue, dos tipos diferentes.
También puede usarlos en funciones, y si usa el parámetro de tipo como un tipo de argumento, también se puede inferir automáticamente.
Los tipos genéricos crean varios "tipos" diferentes de la clase genérica. Esto significa que los campos estáticos se separarán según el tipo de clase y luego List<string>
no comparte datos con List<int>
.
Por esta razón, debe pasarle un parámetro de tipo si desea hacer referencia directamente al nombre de la clase. Esto puede ser un problema en algunos casos, por lo que, alternativamente, si necesita admitir varios tipos, puede transmitir hacia y desde object
usando una técnica llamada boxeo.
Delegados
Los delegados son una forma de empaquetar métodos en variables. Esto es útil para la inyección de dependencias, que es un nombre demasiado extravagante para un concepto simple: las clases flexibles deben obtener algunos valores, llamados dependencias, de sus variables de construcción, lo que permite al usuario de esa clase especificar dependencias a Placer.
Los delegados le permiten hacer esto con funciones. Puede hacer que la clase realice cualquier tipo de acción y no se preocupe por la implementación. Estos todavía se escriben de forma estática: deberá definir los parámetros de entrada y salida como lo haría con cualquier función, excepto marcarlos con "delegar" y no escribir un cuerpo.
Luego puede asignar una función a esta variable y luego usar el variable para llamar a la función. Puede hacerlo directamente como se muestra, o puede usar myDelegate.Invoke()
, que hace lo mismo pero con más detalle.
Puede leer más sobre los delegados en nuestra guía para usarlos.
Indexadores
Las clases en C # usan campos para almacenar datos y propiedades para exponer esos datos a otras clases. Las propiedades son realmente solo un método que expone un campo para que pueda acceder a él haciendo class.property
.
Puede hacer lo mismo para la indexación, p. Ej. class[index]
. Se puede utilizar para crear un comportamiento de indexación personalizado. Por ejemplo, puede crear una lista 2D a partir de una List<T>
creando un indexador personalizado que devuelve un valor basado en los argumentos de entrada.
Deja una respuesta