Archives

gravatar

Incorporar hojas de propiedades (PropertyGrid) a nuestros proyectos

image El control PropertyGrid (hoja o rejilla de propiedades), es un componente que podemos incluir en nuestros proyectos, como editor de propiedades de aquellos controles u objetos contenidos en el mismo. Su utilización nos permite modificar el valor de una propiedad en tiempo de ejecución, del mismo modo que la ventana de propiedades del IDE de Visual Studio nos permite hacerlo en tiempo de diseño.

Su estructura está formada por dos columnas y tantas filas, como propiedades tenga el control u objeto asociado. La primera columna contiene el nombre de la propiedad y la segunda el valor de la misma. Dichos valores pueden ser introducidos directamente o a través del editor apropiado, que variará en función del tipo de valor.

El control PropertyGrid nos permite dotar de una mayor funcionalidad a nuestras aplicaciones, es una interesante forma de mecanizar la manipulación de propiedades,  y ofrece al usuario la posibilidad de personalizar aquellos aspectos para los que hubiese sido programado.

AÑADIENDO UN CONTROL PropertyGrid A NUESTRO PROYECTO

En primer lugar debemos añadir a nuestro proyecto un control PropertyGrid. Ello lo podemos realizar con la siguiente línea de código.

PropertyGrid HojaPropiedades = new PropertyGrid(); 

 

ASOCIANDO CONTROLES Y OBJETOS

Como hemos dicho anteriormente, un control PropertyGrid estará normalmente asociado a un control (instancia de una clase perteneciente a Visual Studio u otra suite de terceros), o a un objeto (instancia de una clase creada por nosotros). El control u objeto asociado se seleccionará mediante el uso de la propiedad SelectedObject, y como valor se introducirá el nombre de la instancia elegida.

HojaPropiedades.SelectedObject = Label1; //Selecciona las propiedades del control Label1

HojaPropiedades.SelectedObject = Button1; //Selecciona las propiedades del control Button1

HojaPropiedades.SelectedObject = Entidad3D; //Selecciona las propiedades del objeto Entidad3D

En los dos primeros casos, el control HojaPropiedades mostraría las propiedades de cada uno de los controles asociados. En el tercer caso, las propiedades mostradas serían las que la clase de la que deriva la instancia Entidad3D, tuviese declaradas.

 

DEFINIENDO LAS PROPIEDADES DE UNA CLASE

Para definir una propiedad dentro de una clase se utiliza la siguiente sintaxis:

[Atributos de la Propiedad]
TipoPropiedad NombrePropiedad
{
  set  {código para la escritura de los valores}
  get  {código para la lectura de los valores}
}
 

Así de este modo, la definición de la clase Entidad3D, quedaría de la siguiente forma.

   1: public class Entidad3D
   2:     {
   3:         /// Los campos siguientes son los campos privados de las propiedades
   4:         /// visibles en la hoja de propiedades.
   5:         /// categoría General
   6:         private string _Nombre;
   7:         private string _Clase;
   8:         private Color _ColorLineas = SystemColors.ControlLight;
   9:         private Color _ColorFondo = SystemColors.ControlDarkDark;
  10:         private string _Grupo;
  11:         /// categoría Coordenadas XYZ
  12:         private XYZ _Coordenadas=new XYZ();
  13:         private XYZ _CoordenadasEjes=new XYZ();
  14:         /// categoría Modificadores 3D
  15:         private float _EscalaX;
  16:         private float _EscalaY;
  17:         private float _EscalaZ;
  18:         private float _RotacionX;
  19:         private float _RotacionY;
  20:         private float _RotacionZ;
  21:         /// categoría Estados
  22:         private bool _Activada;
  23:         private bool _Visible;
  24:         private Font _Fuente = new Font("Arial", 8, FontStyle.Regular); 
  25:  
  26:         /// Definición del código para las propiedades
  27:         [CategoryAttribute("General"),
  28:         DescriptionAttribute ("Establece el nombre para la instancia de la clase."),
  29:         DefaultValueAttribute("Entidad")]
  30:         public string Nombre
  31:         {
  32:             get{return _Nombre;}
  33:             set{_Nombre = value;}
  34:         }
  35:         [CategoryAttribute("General"),
  36:         DescriptionAttribute ("Muestra el nombre de la clase origen."),
  37:         ReadOnlyAttribute(true)]
  38:         public string Clase
  39:         {
  40:             get{return _Clase;}
  41:             set { _Clase = value; }
  42:         }
  43:         [CategoryAttribute("General"),
  44:         DescriptionAttribute ("Establece el color de las líneas en la representación alámbrica.")]
  45:         public Color ColorLineas
  46:         {
  47:             get{return _ColorLineas;}
  48:             set{_ColorLineas = value;}
  49:         }
  50:         [CategoryAttribute("General"),
  51:         DescriptionAttribute("Establece el color del fondo en la representación alámbrica.")]
  52:         public Color ColorFondo
  53:         {
  54:             get { return _ColorFondo; }
  55:             set { _ColorFondo = value; }
  56:         }
  57:         [CategoryAttribute("General"),
  58:         DescriptionAttribute ("Muestra el grupo al que pertenece la entidad."),
  59:         DefaultValueAttribute("Ninguno"),
  60:         ReadOnlyAttribute(true)]
  61:         public string Grupo
  62:         {
  63:             get { return _Grupo; }
  64:             set { _Grupo = value; }
  65:         }
  66:         [CategoryAttribute("Coordenadas XYZ"),
  67:         DescriptionAttribute ("Desplegar para establecer las coordenadas de la entidad."),
  68:         DefaultValueAttribute("0, 0, 0")]
  69:         public XYZ Coordenadas
  70:         {
  71:             get{return _Coordenadas;}
  72:             set{_Coordenadas = value;}
  73:         }
  74:         [CategoryAttribute("Coordenadas XYZ")]
  75:         public XYZ CoordenadasEjes
  76:         {
  77:             get{return _CoordenadasEjes;}
  78:             set{_CoordenadasEjes = value;}
  79:         }
  80:         [CategoryAttribute("Modificadores 3D")]
  81:         public float EscalaX
  82:         {
  83:             get{return _EscalaX;}
  84:             set{_EscalaX = value;}
  85:         }
  86:         [CategoryAttribute("Modificadores 3D")]
  87:         public float EscalaY
  88:         {
  89:             get{return _EscalaY;}
  90:             set{_EscalaY = value;}
  91:         }
  92:         [CategoryAttribute("Modificadores 3D")]
  93:         public float EscalaZ
  94:         {
  95:             get{return _EscalaZ;}
  96:             set{_EscalaZ = value;}
  97:         }
  98:         [CategoryAttribute("Modificadores 3D")]
  99:         public float RotacionX
 100:         {
 101:             get{return _RotacionX;}
 102:             set{_RotacionX = value;}
 103:         }
 104:         [CategoryAttribute("Modificadores 3D")]
 105:         public float RotacionY
 106:         {
 107:             get{return _RotacionY;}
 108:             set{_RotacionY = value;}
 109:         }
 110:         [CategoryAttribute("Modificadores 3D")]
 111:         public float RotacionZ
 112:         {
 113:             get{return _RotacionZ;}
 114:             set{_RotacionZ = value;}
 115:         }
 116:         [CategoryAttribute("Estados")]
 117:         public bool Activada
 118:         {
 119:             get{return _Activada;}
 120:             set {_Activada = value;}
 121:         }
 122:         [CategoryAttribute("Estados")]
 123:         public bool Visible
 124:         {
 125:             get{return _Visible;}
 126:             set{_Visible = value;}
 127:         }
 128:         public Font Fuente
 129:         {
 130:             get{return _Fuente;}
 131:             set{_Fuente = value;}
 132:         }
 133:     }
    
ATRIBUTOS DE LAS PROPIEDADES

En la definición de las propiedades de la clase Entidad3D, se han utilizado algunos atributos especiales. Ello es debido, a que podemos configurar determinadas características de las propiedades de las clases cuyos objetos asociamos a un control PropertyGrid, mediante atributos opcionales incluidos en el espacio System.ComponentModel. Dichos atributos son los siguientes.

  • CategoryAttribute(String) Clasifica las propiedades en grupos.

  • DescriptionAttribute(String) Texto descriptivo que aparece en la parte inferior del PropertyGrid cuando se selecciona la propiedad.

  • BrowsableAttribute(Boolean) Determina si la propiedad se muestra o no en el control. Por defecto el valor es True (verdadero).

  • ReadOnlyAttribute(Boolean) Permite o prohíbe al usuario editar la propiedad para introducir valores. Por defecto el valor es False (falso).

  • DefaultValueAttribute(Object) Establece el valor por defecto de la propiedad.

  • DefaultPropertyAttribute(String) Este atributo no se aplica a una propiedad sino a la clase. Establece la propiedad que aparece seleccionada cuando se entrega el objeto al control.

 
RESULTADO FINAL

imageEl resultado de lo expuesto en las líneas anteriores sería una hoja de propiedades del objeto Entidad3D, tal y como podemos observar en la imagen de la izquierda.

Espero que os haya gustado.

Un saludo y hasta otra.




gravatar

Aplicando estilos con Infragistics AppStylist

Infragistics NetAdvantage for .NET es una completa y detallada suite de desarrollo para ASP.NET, que incluye nuevos controles para su uso dentro de formularios, nuevos componentes y herramientas para la plataforma .NET. Particularmente es la colección de controles que más me gusta de todas las existentes (a parte de Xtreme ToolkitPro de Codejock Software), aunque su implementación dentro de nuestras aplicaciones puede resultar algo compleja.
 
image
 
Una de las herramientas más atractivas de NetAdvantage for .NET, es AppStylist for Windows Forms. Mediante ella podemos personalizar el aspecto visual de nuestras aplicaciones dotándolas de un look completamente profesional, al estilo de Office 2007. No debemos olvidar que la personalización solo afectará a los controles incluidos en la suite NetAdvantage, y no a los pertenecientes a Visual Studio o a otros desarrolladores.
 
Seguir Leyendo...
Su utilización es bastante sencilla, permitiéndonos configurar tanto los colores como los estilos de visualización. Una vez modificadas las propiedades deseadas (en la mayoría de los casos basta con seleccionar una paleta de colores y dejar el resto a la aplicación), podremos guardar el estilo recien creado en  un fichero que posteriormente utilizaremos para dar el look definitivo a nuestra aplicación.
 
Es ahí donde echaremos mano de la programación para insertar las líneas de código adecuadas.
 
 
[Ejemplo en Visual Basic]

Public Sub New()
   
MyBase.New()

 

    'Windows Form Designer requiere la siguiente línea de código.

    InitializeComponent()

 

    'La estilización de la aplicación comienza cuando la librería de estilos

    'es cargada  mediante una llamada al método Load del StyleManager. Esto

    'ocurre normalmente durante el arranque de la aplicación.

    Infragistics.Win.AppStyling.StyleManager.Load("MiEstilo.isl") 

 

    'Dentro de la librería existirá un estilo marcado como estilo por

    'defecto. Además pueden existir otros estilos marcados por defecto para

    'tipos concretos de componentes como UltraGrid, UltraCombo, etc.

 

    'En el siguiente ejemplo, no queremos usar el estilo por defecto para

    'el componente UltraToolbarsManager, sino otro específico,  por lo que

    'cambiamos el valor de la propiedad StyleSetName, asignándole el nombre

    'del estilo deseado.



    Me.UltraToolbarsManager1.StyleSetName = "EstiloDeComponente"

 

    'En el caso de que no quisiéramos asignar estilo alguno a un componente,

    'desactivaríamos su capacidad de estilización.

 

    Me.UltraButton1.UseAppStyling = False

End Sub

 

[Ejemplo en C#]

public MainForm()
{

    //Windows Form Designer requiere la siguiente línea de código.

   InitializeComponent();

 

    //La estilización de la aplicación comienza cuando la librería de

    //estilos es cargada  mediante una llamada al método Load del

    //StyleManager. Esto ocurre normalmente durante el arranque de la

    //aplicación.

   Infragistics.Win.AppStyling.StyleManager.Load("MiEstilo.isl");

 

    //Dentro de la librería existirá un estilo marcado como estilo por

    //defecto. Además pueden existir otros estilos marcados por defecto para

    //tipos concretos de componentes como UltraGrid, UltraCombo, etc.

 

    //En el siguiente ejemplo, no queremos usar el estilo por defecto para

    //el  componente UltraToolbarsManager, sino otro específico,  por lo que

    //cambiamos el valor de la propiedad StyleSetName, asignándole el nombre

    //del estilo deseado.

    this.ultraToolbarsManager1.StyleSetName = "EstiloDeComponente";

 

    //En el caso de que no quisiéramos asignar estilo alguno a un

    //componente, desactivaríamos su capacidad de estilización.

    this.ultraButton1.UseAppStyling = false;
}

 
 
Y he aquí, que con estas escuetas líneas de código, habremos conseguido dotar de un aspecto único a los formularios y componentes de nuestra aplicación.
 
Espero que os haya gustado.
 
Un saludo y hasta otra.
 

gravatar

El lenguaje de programación C#. Aspectos léxicos.

Autor: José Antonio González Seco

Curso completo disponible en formato DOC y PDF en la web del autor.

Comentarios

Un comentario es texto que se incluye en el código fuente para facilitar su lectura a los programadores y cuyo contenido es, por defecto, completamente ignorado por el compilador. Suelen usarse para incluir información sobre el autor del código, para aclarar el significado o el porqué de determinadas secciones de código, para describir el funcionamiento de los métodos de las clases, etc.

En C# hay dos formas de escribir comentarios. La primera consiste en encerrar todo el texto que se desee comentar entre caracteres /* y */ siguiendo la siguiente sintaxis:
/*<texto>*/
Estos comentarios pueden abarcar tantas líneas como sea necesario. Por ejemplo:
/* Esto es un comentario
que ejemplifica cómo se escribe comentarios que ocupen varias líneas  */  
Ahora bien, hay que tener cuidado con el hecho de que no es posible anidar comentarios de este tipo. Es decir, no vale escribir comentarios como el siguiente:
/*  Comentario contenedor /* Comentario contenido */  */
Esto se debe a que como el compilador ignora todo el texto contenido en un comentario y sólo busca la secuencia */ que marca su final, ignorará el segundo /* y cuando llegue al primer */ considerará que ha acabado el comentario abierto con el primer /* (no el abierto con el segundo) y pasará a buscar código. Como el */ sólo lo admite si ha detectado antes algún comentario abierto y aún no cerrado (no mientras busca código), cuando llegue al segundo */ considerará que ha habido un error ya que encontrará el */ donde esperaba encontrar código.

Dado que muchas veces los comentarios que se escriben son muy cortos y no suelen ocupar más de una línea, C# ofrece una sintaxis alternativa más compacta para la escritura este tipo de comentarios en las que se considera como indicador del comienzo del comentario la pareja de caracteres // y como indicador de su final el fin de línea. Por tanto, la sintaxis que siguen estos comentarios es:
// <texto>
Y un ejemplo de su uso es:
// Este comentario ejemplifica como  escribir comentarios abreviados de 
una sola línea
Estos comentarios de una sola línea sí que pueden anidarse sin ningún problema. Por ejemplo, el siguiente  comentario es perfectamente válido:
// Comentario contenedor //  Comentario contenido
Seguir Leyendo...

Identificadores

Al igual que en cualquier lenguaje de programación, en C# un identificador no es más que, como su propio nombre indica, un nombre con el que identificaremos algún elemento de nuestro código, ya sea una clase, una variable, un método, etc.

Típicamente el nombre de un identificador será una secuencia de cualquier número de caracteres alfanuméricos –incluidas vocales acentuadas y eñes- tales que el primero de ellos no sea un número. Por ejemplo, identificadores válidos serían: Arriba, caña, C3P0, áëÎò, etc; pero no lo serían 3com, 127, etc.

Sin embargo, y aunque por motivos de legibilidad del código no se recomienda, C# también permite incluir dentro de un identificador caracteres especiales imprimibles tales como símbolos de diéresis, subrayados, etc. siempre y cuando estos no tengan un significado especial dentro del lenguaje. Por ejemplo, también serían identificadores válidos, _barco_, c¨k y A·B; pero no C# (# indica inicio de directiva de preprocesado) o a!b (! indica operación lógica “not”)

Finalmente, C# da la posibilidad de poder escribir identificadores que incluyan caracteres Unicode que no se puedan imprimir usando el teclado de la máquina del programador o que no sean directamente válidos debido a que tengan un significado especial en el lenguaje. Para ello, lo que permite es escribir estos caracteres usando secuencias de escape, que no son más que secuencias de caracteres con las sintaxis:
     \u<dígito><dígito><dígito><dígito>
ó    \U<dígito><dígito><dígito><dígito><dígito><dígito><dígito><dígito>
Estos dígitos indican es el código Unicode del carácter que se desea incluir como parte del identificador, y cada uno de ellos ha de ser un dígito hexadecimal válido. (0-9, a-f ó A-F) Hay que señalar que el carácter u ha de escribise en minúscula cuando se indiquen caracteres Unicode con 4 dígitos y en mayúscula cuando se indiquen con caracteres  de ocho. Ejemplos de identificadores válidos son C\u0064 (equivale a C#, pues 64 es el código de # en Unicode) ó a\U00000033b (equivale a a!b>)


Palabras reservadas

Aunque antes se han dado una serie de restricciones sobre cuáles son los nombres válidos que se pueden dar en C# a los identificadores, falta todavía por dar una: los siguientes nombres no son válidos como identificadores ya que tienen un significado especial en el lenguaje:
abstract, as, base, bool, break, byte, case, catch, char, checked, class, const, continue, decimal, default, delegate, do, double, else, enum, event, explicit, extern, false, finally, fixed, float, for, foreach, goto, if, implicit, in, int, interface, internal, lock, is, long, namespace, new, null, object, operator, out, override, params, private, protected, public, readonly, ref, return, sbyte, sealed, short, sizeof, stackalloc, static, string, struct, switch, this, throw, true, try, typeof, uint, ulong, unchecked, unsafe, ushort, using, virtual, void, while
Aparte de estas palabras reservadas, si en futuras implementaciones del lenguaje se decidiese incluir nuevas palabras reservadas, Microsoft dice que dichas palabras habrían de incluir al menos dos símbolos de subrayado consecutivos (__>) Por tanto, para evitar posibles conflictos futuros no se recomienda dar a nuestros identificadores nombres que contengan dicha secuencia de símbolos.

Aunque directamente no podemos dar estos nombres a nuestros identificadores, C# proporciona un mecanismo para hacerlo indirectamente y de una forma mucho más legible que usando secuencias de escape. Este mecanismo consiste en usar el carácter @  para prefijar el nombre coincidente con el de una palabra reservada que queramos dar a nuestra variable. Por ejemplo, el siguiente código es válido:
class @class
{
  static void @static(bool @bool)
  {
    if (@bool)
      Console.WriteLine("cierto");
    else
      Console.WriteLine("falso");
  }
}
Lo que se ha hecho en el código anterior ha sido usar @ para declarar una clase de nombre class con un método de nombre static que toma un parámetro de nombre bool, aún cuando todos estos nombres son palabras reservadas en C#.

Hay que  precisar que aunque el nombre que nosotros escribamos sea por ejemplo @class, el nombre con el que el compilador va a tratar internamente al identificador es solamente class. De hecho, si desde código escrito en otro lenguaje adaptado a .NET distinto a C# hacemos referencia a éste identificador y en ese lenguaje su nombre no es una palabra reservada, el nombre con el que deberemos referenciarlo es class, y no @class (si también fuese en ese lenguaje palabra reservada habría que referenciarlo con el mecanismo que el lenguaje incluyese para ello, que quizás también podría consistir en usar @ como en C#)

En realidad, el uso de @ no se tiene porqué limitar a preceder palabras  reservadas en C#, sino que podemos  preceder cualquier nombre con él. Sin embargo, hacer esto no se recomienda, pues es considerado como un mal hábito de programación y puede provocar errores muy sutiles como el que muestra el siguiente ejemplo:
class A
{
  int  a;     // (1)
  int  @a; // (2)

public static void Main() {} }
Si intentamos compilar este código se producirá un error que nos informará de que el campo de nombre a ha sido declarado múltiples veces en la clase A. Esto se debe a que como @ no forma parte en realidad del nombre del identificador al que precede, las declaraciones marcadas con comentarios como (1) y (2) son equivalentes.

Hay que señalar por último una cosa respecto al carácter @: sólo puede  preceder al nombre de un identificador,  pero no puede estar contenido dentro del mismo. Es decir, identificadores como i5322@fie.us.es no son válidos.


Literales

Un literal es la representación explícita de los valores que pueden tomar los tipos básicos del lenguaje. A continuación se explica cuál es la sintaxis con que se escriben los literales en C# desglosándolos según el tipo de valores que representan:

  • Literales enteros: Un número entero se puede representar en C# tanto en formato decimal como hexadecimal. En el primer caso basta escribir los dígitos decimales (<0-9) del número unos tras otros, mientras que en el segundo hay que preceder los dígitos hexadecimales (0-9, a-f, A-F) con el prefijo 0x. En ambos casos es posible preceder el número de los operadores + ó para indicar si es positivo o negativo, aunque si no se pone nada se considerará que es positivo. Ejemplos de literales enteros son 0, 5, +15, -23, 0x1A, -0x1a, etc

    En realidad, la sintaxis completa para la escritura de literales enteros también puede incluir un sufijo que indique el tipo de dato entero al que ha de pertenecer el literal. Esto no lo veremos hasta el Tema 7: Variables y tipos de datos.

  • Literales reales: Los números reales se escriben de forma similar a los enteros, aunque sólo se pueden escribir en forma decimal y para separar la parte entera de la real usan el tradicional punto decimal (carácter .) También es posible representar los reales en formato científico, usándose para indicar el exponente los caracteres >e ó E. Ejemplos de literales reales son 0.0, 5.1, -5.1, +15.21, 3.02e10, 2.02e-2, 98.8E+1, etc.

    Al igual que ocurría con los literales enteros, los literales reales también pueden incluir sufijos que indiquen el tipo de dato real al que pertenecen, aunque nuevamente no los veremos hasta el Tema 7: Variables y tipos de datos

  • Literales lógicos: Los únicos literales lógicos válidos son true y false, que respectivamente representan los valores lógicos cierto y falso.

  • Literales de carácter: Prácticamente cualquier carácter se puede representar encerrándolo entre comillas simples. Por ejemplo, 'a' (letra a), ' ' (carácter de espacio), '?' (símbolo de interrogación), etc. Las únicas excepciones a esto son los caracteres que se muestran en la Tabla 4.1, que han de representarse con secuencias de escape que indiquen su valor como código Unicode o mediante un formato especial tal y como se indica a continuación:
Carácter

Código de escape Unicode

Código de escape especial

Comilla simple

\u0027

\'

Comilla doble

\u0022

\″

Carácter nulo

\u0000

\0

Alarma

\u0007

\a

Retroceso

\u0008

\b

Salto de página

\u000C

\f

Nueva línea

\u000A

\n

Retorno de carro

\u000D

\r

Tabulación horizontal

\u0009

\t

Tabulación vertical

\u000B

\v

Barra invertida

\u005C

\\

Tabla 4.1: Códigos de escape especiales



En realidad, de la tabla anterior hay que matizar que el carácter de comilla doble también puede aparecer dentro de        un literal de cadena directamente, sin necesidad de usar secuencias de escape. Por tanto, otros ejemplos de literales de carácter válidos serán '\″', '″', '\f', '\u0000', '\\', '\'', etc.

Aparte de para representar los caracteres de la tabla anterior, también es posible usar los códigos de escape Unicode          para representar cualquier código Unicode, lo que suele usarse para representar literales de caracteres no incluidos en los teclados estándares.

Junto al formato de representación de códigos de escape Unicode ya visto, C# incluye un formato abreviado para representar estos códigos en los literales de carácter si necesidad de escribir siempre cuatro dígitos aún cuando el código a representar tenga muchos ceros en su parte izquierda. Este formato consiste en preceder el código de \x en vez de \u. De este modo, los literales de carácter ‘\U00000008’, '\u0008', '\x0008', '\x008', '\x08' y '\x8' son todos equivalentes. Hay que tener en cuenta que este formato abreviado sólo es válido en los literales de carácter, y no a la hora de dar nombres a los identificadores.
    • Literales de cadena: Una cadena no es más que una secuencia de caracteres encerrados entre comillas dobles. Por ejemplo ″Hola, mundo″, ″camión″, etc. El texto contenido dentro estos literales puede estar formado por cualquier número de literales de carácter concatenados y sin las comillas simples, aunque si incluye comillas dobles éstas han de escribirse usando secuencias de escape porque si no el compilador las interpretaría como el final de la cadena.

      Aparte del formato de escritura de literales de cadenas antes comentado, que es el comúnmente utilizado en la mayoría de lenguajes de programación, C# también admite uno nuevo consistente en precederlos de un símbolo @, caso en que todo el contenido de la cadena sería interpretado tal cual, sin considerar la existencia de secuencias de escape. A este tipo de literales se les conoce como literales de cadena planos o literales verbatim y pueden incluso ocupar varias líneas. La siguiente tabla recoge algunos ejemplos de cómo se interpretan:

      Literal de cadena

      Interpretado como...

      ″Hola\tMundo″

      Hola         Mundo

      @”Hola\tMundo″

      Hola\tMundo

      @″Hola

           Mundo″

      Hola

           Mundo

      @”””Hola Mundo””″

      “Hola Mundo”

      Tabla 4.2: Ejemplos de literales de cadena planos


      El último ejemplo de la tabla se ha aprovechado para mostrar que si dentro de un literal de cadena plano se desea incluir caracteres de comilla doble sin que sean confundidos con el final de la cadena basta duplicarlos.
    • Literal nulo: El literal nulo es un valor especial que se representa en C# con la palabra reservada null y se usa como valor de las variables de objeto no inicializadas para así indicar que contienen referencias nulas.

    Operadores

    Un operador en C# es un símbolo formado por uno o más caracteres que permite realizar una determinada operación entre uno o más datos y produce un resultado.

    A continuación se describen cuáles son los operadores incluidos en el lenguaje clasificados según el tipo de operaciones que permiten realizar, aunque hay que tener en cuenta que C# permite la redefinición del significado de la mayoría de los operadores según el tipo de dato sobre el que se apliquen,  por lo que lo que aquí se cuenta se corresponde con los usos más comunes de los mismos:

    • Operaciones aritméticas: Los operadores aritméticos incluidos en C# son los típicos de suma (+>), resta (-), producto (*), división (/) y módulo (%) También se incluyen operadores de “menos unario” () y “más unario” (+)

      Relacionados con las operaciones aritméticas se encuentran un par de operadores llamados checked y unchecked que permiten controlar si se desea detectar los desbordamientos que puedan producirse si al realizar este tipo de  operaciones el resultado es superior a la capacidad del tipo de datos de sus operandos. Estos operadores se usan así:
      checked (<expresiónAritmética>)
      unchecked(<expresiónAritmética>)
      Ambos operadores calculan el resultado de <expresiónAritmética> y lo devuelven si durante el cálculo no se produce ningún desbordamiento. Sin embargo, en caso de que haya desbordamiento cada uno actúa de una forma distinta: checked provoca un error de compilación si <expresiónAritmética> es una expresión constante y una excepción System.OverflowException si no lo es, mientras que unchecked devuelve el resultado de la expresión aritmética truncado para que quepa en el tamaño esperado.

      Por defecto, en ausencia de los operadores checked y unchecked lo que se hace es evaluar las operaciones aritméticas entre datos constantes como si se les aplicase checked y las operaciones entre datos no constantes como si se les hubiese aplicado unchecked.
    • Operaciones lógicas: Se incluyen operadores que permiten realizar las operaciones lógicas típicas: “and” (&& y &), “or” (|| y |), “not” (!) y “xor” (^)

      Los operadores && y || se diferencia de & y | en que los primeros realizan evaluación perezosa y los segundos no. La evaluación perezosa consiste en que si el resultado de evaluar el primer operando permite deducir el resultado de la operación, entonces no se evalúa el segundo y se devuelve dicho resultado directamente, mientras que la evaluación no perezosa consiste en evaluar siempre ambos operandos. Es decir, si el primer operando de una operación && es falso se devuelve false directamente, sin evaluar el segundo; y si el primer operando de una || es cierto se devuelve true directamente, sin evaluar el otro.

      • Operaciones relacionales: Se han incluido los tradicionales operadores de igualdad (==), desigualdad (!=), “mayor que” (>), “menor que” (<), “mayor o igual que” (>=) y “menor o igual que” (<=)
      • Operaciones de manipulación de bits: Se han incluido operadores que permiten realizar a nivel de bits operaciones “and” (&), “or” (|), “not” (~), “xor” (^), desplazamiento a izquierda (<<) y desplazamiento a  derecha (>>) El operador << desplaza a izquierda rellenando con ceros, mientras que el tipo de relleno realizado por >> depende del tipo de dato sobre el que se aplica: si es un dato con signo mantiene el signo, y en caso contrario rellena con ceros.
      • Operaciones de asignación: Para realizar asignaciones se usa en C# el operador =, operador que además de realizar la asignación que se le solicita devuelve el valor asignado.  Por ejemplo, la expresión a = b asigna a la variable a el valor de la variable b y devuelve dicho valor, mientras que la expresión c = a = b asigna a las variables c y a el valor de b (el operador = es asociativo por la derecha)

        También se han incluido operadores de asignación compuestos que permiten ahorrar tecleo a  la hora de realizar asignaciones tan comunes como:
        temperatura =  temperatura + 15;   // Sin usar  asignación compuesta
        temperatura += 15;                       // Usando asignación compuesta
        Las dos líneas anteriores son equivalentes, pues el operador compuesto += asigna            a su primer operando el valor que tenía más el de su segundo operando (o sea, le suma el segundo operando) Como se ve, permite compactar bastante el código.

        Aparte del operador de asignación compuesto +=, también se ofrecen operadores de asignación compuestos para la mayoría de los operadores binarios ya vistos. Estos son: +=, -=, *=, /=, %=, &=, |=, ^=, <<= y >>=. Nótese que no hay versiones compuestas para los operadores binarios && y ||>.

        Otros dos operadores de asignación incluidos son los de incremento(++) y decremento (--) Estos operadores permiten, respectivamente, aumentar y disminuir en una unidad el valor de la variable sobre el que se aplican. Así, estas líneas de código son equivalentes:
        temperatura = temperatura +  1;  // Sin usar asignación compuesta ni  incremento
        temperatura += 1;               // Usando asignación  compuesta
        temperatura++;                  // Usando incremento
        Si el operador ++ se coloca tras el nombre de la variable (como en el ejemplo) devuelve el valor de la variable antes de incrementarla, mientras que si se coloca antes, devuelve el valor de ésta tras incrementarla; y lo mismo ocurre con el operador --. Por ejemplo:
        c = b++; // Se asigna a  c el valor de b y luego se incrementa b
        c = ++b; // Se  incrementa el valor de b y luego se asigna a c
        La ventaja de usar los operadores ++ y -- es que en muchas máquinas son más eficientes que el resto de formas de realizar sumas o restas de una unidad, pues el compilador puede traducirlos en una única instrucción en código máquina.
      • Operaciones con cadenas: Para realizar operaciones de concatenación de cadenas se puede usar el mismo operador que para realizar sumas, ya que en C# se ha redefinido su significado para que cuando se aplique entre operandos que sean cadenas o que sean una cadena y un carácter lo que haga sea concatenarlos. Por ejemplo, ″Hola″+″ mundo″ devuelve ″Hola mundo″, y ″Hola mund″ + ′o′ también.
      • Operaciones de acceso a tablas: Una tabla es un conjunto de ordenado de objetos de tamaño fijo. Para acceder a cualquier elemento de este conjunto se aplica el operador postfijo [] sobre la tabla para indicar entre corchetes la posición que ocupa el objeto al que se desea acceder dentro del conjunto. Es decir, este operador se usa así:
        [<posiciónElemento>]
        Un ejemplo de su uso en el que se asigna al elemento que ocupa la posición 3 en una tabla de nombre tablaPrueba el valor del elemento que ocupa la posición 18 de dicha tabla es el siguiente:
        tablaPrueba[3] =  tablaPrueba[18];
        Las tablas se estudian detenidamente en el Tema 7: Variables y tipos de datos
      • Operador condicional: Es el único operador incluido en C# que toma 3 operandos, y se usa así:
        <condición> ? <expresión1> : <expresión2>
        El significado del operando es el siguiente: se evalúa <condición> Si es cierta se devuelve el resultado de evaluar <expresión1>, y si es falsa se devuelve el resultado de evaluar <condición2>. Un ejemplo de su uso es:
        b = (a>0)? a :  0;      // Suponemos a y b de tipos  enteros
        En este ejemplo, si el valor de la variable a es superior a 0 se asignará a b el valor de a, mientras que en caso contrario el valor que se le asignará será 0.

        Hay que tener en cuenta que este operador es asociativo por la derecha, por lo que una expresión como a?b:c?d:e es equivalente a a?b:(c?d:e)

        No hay que confundir este operador con la instrucción condicional if que se tratará en el Tema 8:Instrucciones, pues aunque su utilidad es similar al de ésta,  ? devuelve  un valor e if no.
      • Operaciones de delegados: Un delegado es un objeto que puede almacenar en referencias a uno o más métodos y a través del cual es posible llamar a estos métodos. Para añadir objetos a un delegado se usan los operadores + y +=, mientras que para quitárselos se usan los operadores y -=. Estos conceptos se estudiarán detalladamente en el Tema 13: Eventos y delegados
      • Operaciones de acceso a objetos: Para acceder a los miembros de un objeto se usa el operador ., cuya sintaxis es:
        <objeto>.<miembro>
        Si a es un objeto, ejemplos de cómo llamar a diferentes miembros suyos son:
        a.b = 2;  // Asignamos a su propiedad a el valor 2
        a.f();    // Llamamos a su método f()
        a.g(2); // Llamamos a su método g()  pasándole como parámetro el valor entero 2
        No se preocupe si no conoce los conceptos de métodos, propiedades, eventos y delegados en los que se basa este ejemplo, pues se explican detalladamente en temas posteriores.
      • Operaciones con punteros: Un puntero es una variable que almacena una referencia a una dirección de memoria. Para obtener la dirección de memoria de un objeto se usa el operador &, para acceder al contenido de la dirección de memoria almacenada en un puntero se usa el operador *, para acceder a un miembro de un objeto cuya dirección se almacena en un puntero se usa ->, y para referenciar una dirección de memoria de forma relativa a un puntero se le aplica el operador [] de la forma puntero[desplazamiento]. Todos estos conceptos se explicarán más a fondo en el Tema 18: Código inseguro.
      • Operaciones de obtención de información sobre tipos: De todos los operadores que nos permiten obtener información sobre tipos de datos el más importante es typeof, cuya forma de uso es:
        typeof(<nombreTipo>)
        Este operador devuelve un objeto de tipo System.Type con información sobre el tipo de nombre <nombreTipo> que podremos consultar a través de los miembros  ofrecidos por dicho objeto. Esta información incluye detalles tales como cuáles son sus miembros, cuál es su tipo padre o a qué espacio de nombres pertenece.

        Si lo que queremos es determinar si una determinada expresión es de un tipo u     otro, entonces el operador a usar es is, cuya sintaxis es la siguiente:
        <expresión> is <nombreTipo>
        El significado de este operador es el siguiente: se evalúa <expresión>.

        Si el resultado de ésta es del tipo cuyo nombre se indica en <nombreTipo> se devuelve true; y si no, se devuelve false. Como se verá en el Tema 5: Clases, este operador suele usarse en métodos polimórficos.

        Finalmente, C# incorpora un tercer operador que permite obtener información sobre un tipo de dato: sizeof Este operador permite obtener el número de bytes que ocuparán en memoria los objetos de un tipo, y se usa así:
        sizeof(<nombreTipo>)
        sizeof sólo puede usarse dentro de código inseguro, que por ahora basta considerar que son zonas de código donde es posible usar punteros. No será hasta el Tema 18: Código inseguro cuando lo trataremos en profundidad.

        Además, sizeof sólo se puede aplicar sobre nombres de tipos de datos cuyos objetos se puedan almacenar directamente en pila. Es decir, que sean estructuras (se verán en el Tema 13) o tipos enumerados (se verán en el Tema 14)
      • Operaciones de creación de objetos: El operador más típicamente usado para crear objetos es new, que se usa así:
        new <nombreTipo>(<parametros>)

        Este operador crea un objeto de <nombreTipo> pasándole a su método constructor los parámetros indicados en <parámetros> y devuelve una referencia al mismo. En función del tipo y número de estos parámetros se llamará a uno u otro de los constructores del objeto. Así, suponiendo que a1 y a2 sean variables de tipo Avión, ejemplos de uso del operador new  son:
        Avión a1 = new Avión();         // Se llama al constructor sin parámetros de Avión
        Avión a2  = new Avión(“Caza”);  // Se llama al constructor de Avión que toma                                                                           // como parámetro una cadena
        En caso de que el tipo del que se haya solicitado la creación del objeto sea una clase, éste se creará en memoria dinámica, y lo que new devolverá será una referencia a la dirección de pila donde se almacena una referencia a la dirección del objeto en memoria dinámica. Sin embargo, si el objeto a crear pertenece a una estructura o a un tipo enumerado, entonces éste se creará directamente en la pila y la referencia devuelta por el new se referirá directamente al objeto creado. Por estas razones, a las clases se les conoce como tipos referencia ya que de sus objetos en pila sólo se almacena una referencia a la dirección de memoria dinámica donde verdaderamente se encuentran; mientras que a las estructuras y tipos enumerados se les conoce como tipos valor ya sus objetos se almacenan directamente en pila.

        C#  proporciona otro operador que también nos permite crear objetos. Éste es stackalloc, y se usa así:
        stackalloc <nombreTipo>[<nElementos>]
        Este operador lo que hace es crear en pila una tabla de tantos elementos de tipo <nombreTipo> como indique <nElementos> y devolver la dirección de memoria en que ésta ha sido creada. Por ejemplo:
        int * p =  stackalloc[100]; // p apunta a una tabla de 100 enteros.
        stackalloc sólo puede usarse para inicializar punteros a objetos de tipos valor declarados como variables locales.
      • Operaciones de conversión: Para convertir unos objetos en otros se utiliza el operador de conversión, que no consiste más que en preceder la expresión a convertir del nombre entre paréntesis del tipo al que se desea convertir el resultado de evaluarla. Por ejemplo, si l es una variable de tipo long y se desea almacenar su valor dentro de una variable de tipo int llamada i, habría que convertir previamente su valor a tipo int así:
        i = (int) l;  // Asignamos a i el resultado de convertir el  valor de l a tipo int
        Los tipos int y long> están predefinidos en C# y permite almacenar valores enteros con signo. La capacidad de int es de 32 bits, mientras que la de long es de 64 bits. Por tanto, a no ser que hagamos uso del operador de conversión, el compilador no nos dejará hacer la asignación, ya que al ser mayor la capacidad de los long, no todo valor que se pueda almacenar en un long tiene porqué poderse almacenar en un int. Es decir, no es válido:
        i = l; //ERROR: El valor  de l no tiene porqué caber en i
        Esta restricción en la asignación la impone el compilador debido a que sin ella podrían producirse errores muy difíciles de detectar ante truncamientos no esperados debido al que el valor de la variable fuente es superior a la capacidad de la variable destino.

        Existe otro operador que permite realizar operaciones de conversión de forma muy similar al ya visto. Éste es el operador as, que se usa así:
        <expresión> as <tipoDestino>
        Lo que hace es devolver el resultado de convertir el resultado de evaluar <expresión>al tipo indicado en <tipoDestino> Por ejemplo, para almacenar en una variable p el resultado de convertir un objeto t a tipo tipo Persona se haría:
        p = t as Persona; 
        Las únicas diferencias entre usar uno u otro operador de conversión son:
      • as sólo es aplicable a tipos referencia y sólo a aquellos casos en que existan conversiones predefinidas en el lenguaje. Como se verá más adelante, esto sólo incluye conversiones entre un tipo y tipos padres suyos y entre  un tipo y tipos hijos suyos.

        Una consecuencia de esto es que el programador puede definir cómo hacer conversiones de tipos por él definidos y otros mediante el operador (), pero no mediante as.Esto se debe a que as únicamente indica que se desea que una referencia a un objeto en memoria dinámica se trate como si el objeto fuese de otro tipo, pero no implica conversión ninguna. Sin embargo, () sí que implica conversión si el <tipoDestino> no es compatible con el tipo del objeto referenciado. Obviamente, el operador se aplicará  mucho más rápido en los casos donde no sea necesario convertir.
      • En caso de que se solicite hacer una conversión inválida as devuelve null mientras que () produce una excepción System.InvalidCastException.

      (C) 2001 José Antonio González Seco