sábado, 17 de junio de 2017

Curso de C#: campos y propiedades

Campos


Ambas cosas (campos y propiedades) representan a los datos de una clase, aunque cada uno de ellos lo hace de un modo diferente. Los campos de una clase se construyen a base de variables. 

class Circunferencia
    {

        public double Radio;
        public double Perimetro;
        public double Area;
        public const double PI=3.1415926;
    }
  
Los modificadores de acceso de los que se habló al comienzo de este curso  (private, protected, internal y public) son aplicables a las variables y constantes solamente cuando estas representan los campos de una clase, y para ello deben estar declaradas como miembros de la misma dentro de su bloque. Sin embargo, una variable que esté declarada en otro bloque distinto (dentro de un método, por ejemplo) no podrá ser un campo de la clase, pues será siempre privada para el código que esté dentro de ese bloque, de modo que no se podrá acceder a ella desde fuera del mismo. 

Curso de C#: campos y propiedades

  
Al instanciar el objeto, se ejecutará su constructor dando los valores adecuados a los campos Area y Perimetro. Sin embargo, después el cliente puede modificar los valores de estos campos, asignándole valores a su antojo y haciendo, por lo tanto, que dichos valores no sean coherentes (claro, si el radio vale 4, el perímetro no puede ser 1, ni el área puede ser 2). Para evitar esta falta de seguridad usamos algo que no existía hasta ahora en ningún otro lenguaje: los campos de sólo lectura (ojo, campos, no propiedades). 

class Circunferencia
{

    public double Radio;
    public readonly double Perimetro;
    public readonly double Area;
    public const double PI=3.1415926;
}

Ahora están protegidos los campos Perimetro y Area, pues son de sólo lectura, de modo que ahora el cliente no podrá modificar los valores de dichos campos. Se ha puesto la palabra readonly delante del tipo del campo. Sin embargo, sigue habiendo un problema: si, después de instanciar la clase, el cliente puede modificar el valor del radio. El radio volvería a no ser coherente con el resto de los datos del objeto. Podría ponerse también el campo Radio como de sólo lectura, pero tendríamos que instanciar un nuevo objeto cada vez que necesitásemos un radio distinto. Podríamos poner el radio también como campo de sólo lectura y escribir un método para que el cliente pueda modificar el radio, y escribir en él, código para modificar los tres campos, de modo que vuelvan a ser coherentes. Esto no se puede hacer porque los campos readonly solamente pueden ser asignados una vez en el constructor, y a partir de aquí su valor es constante y no se puede variar en esa instancia. 

No utilizar constantes en vez de campos de sólo lectura. Pues para poder usar constantes hay que conocer previamente el valor que van a tener (como la constante PI, que siempre vale lo mismo), pero, en este caso, conocemos el los valores hasta que no se ejecute el programa. Los campos de sólo lectura almacenan valores constantes que no se conocerán hasta que el programa esté en ejecución. 

Los campos, igual que los métodos y los constructores, también pueden ser static. Su comportamiento sería parecido: un campo static tiene mucho más que ver con la clase que con una instancia particular de ella. Si deseamos añadir una descripción a la clase circunferencia, podemos usar un campo static, porque todas las instancias de esta clase se ajustarán necesariamente a dicha descripción. Si ponemos el modificador static a un campo de sólo lectura, este campo ha de ser inicializado en un constructor static. Pero las constantes no aceptan el modificador de acceso static: si su modificador de acceso es public o internal ya se comportará como su fuera un campo static. Ejemplo:

class Circunferencia
{
    static Circunferencia()
    {
        Descripcion="Polígono regular de infinitos lados";
    }

    public Circunferencia(double radio)
    {
        this.Radio=radio;
        this.Perimetro=2 * PI * this.Radio;
        this.Area=PI * Math.Pow(this.Radio,2);
    }

    public double Radio;
    public readonly double Perimetro;
    public readonly double Area;
    public const double PI=3.1415926;

    public static readonly string Descripcion;
}

La clase Circunferencia tiene un constructor static que inicializa el valor del campo Descripción, que también es static.  El objetivo es que esta clase contenga siempre datos coherentes, dado que el área y el perímetro siempre estarán en función del radio, y que el radio se pueda modificar sin necesidad de volver a instanciar la clase. No es posible utilizar campos ni campos de sólo lectura, pues los primeros no permiten controlar los datos que contienen, y los segundos no permiten modificar su valor después de ser inicializados en el constructor.

Es posible cambiar los modificadores de acceso de los campos, haciéndolos private o protected en lugar de public, y después escribir métodos para retornar sus valores. Sin embargo, se trata de un modo muy poco intuitivo, y poco natural. 

Propiedades


Las propiedades también representan los datos de los objetos de una clase, pero lo hacen de un modo distinto a los campos. Los campos no nos permiten tener el control de su valor salvo que sean de sólo lectura, y si son de sólo lectura solamente se podían asignar una vez en el constructor. Esto puede ser útil en  ocasiones, pero no siempre. Las propiedades solventan todos estos problemas: por un lado nos permiten tener un control absoluto de los valores que reciben o devuelven, y además no existen limitaciones para modificar y cambiar sus valores tantas veces como sea preciso.

Las propiedades funcionan internamente como si fueran métodos, esto es, ejecutan el código que se encuentra dentro de su bloque, pero se muestran al cliente como si fueran campos, es decir, datos. La sintaxis de una propiedad es la siguiente:

acceso [static] tipo NombrePropiedad
{
    get
    {
        // Código para calcular el valor de retorno (si procede)
        return ValorRetorno;
    }

    set
    {
        // Código para validar y/o asignar el valor de la propiedad
    }
}

El modificador de acceso, puede ser cualquiera de los que se usan también para los campos. Si no se indica, será private. Después la palabra static si se desea definir como propiedad estática, sería accesible sin instanciar objetos de la clase, pero no accesible desde las instancias de la misma (como los campos static). Posteriormente el tipo del dato que almacenará la propiedad (cualquier tipo valor o cualquier tipo referencia), seguido del nombre de la propiedad. Dentro del bloque de la propiedad hay otros dos bloques: el bloque get es el bloque de retorno, el que nos permitirá ver lo que vale la propiedad desde la aplicación cliente; y el bloque set es el bloque de asignación de la propiedad, el que nos permitirá asignarle valores desde la aplicación cliente. El orden en que se pongan los bloques get y set es indiferente. S si se omite el bloque de asignación (set) habremos construido una propiedad de sólo lectura. Y viceversa. Ejemplo:

class Circunferencia
    {
        public Circunferencia(double radio)
        {
            this.radio=radio;
        }

        private double radio;
        const double PI=3.1415926;

        public double Radio
        {
            get
            {
                return this.radio;
            }

            set
            {
                this.radio=value;
            }
        }

        public double Perimetro
        {
            get
            {
                return 2 * PI * this.radio;
            }
        }

        public double Area
        {
            get
            {
                return PI * Math.Pow(this.radio, 2);
            }
        }
    }

No se han escrito métodos para modificar el radio ni para obtener los valores de las otros datos, sino que se han escrito propiedades. Con esto  se consigue que el cliente pueda acceder a los datos de un modo mucho más natural. A continuación se muestra un ejemplo de método Main:

static void Main()
{
      Circunferencia c=new Circunferencia(4);
      Console.WriteLine("El radio de la circunferencia es {0}",c.Radio);
      Console.WriteLine("El perímetro de la circunferencia es {0}",
    c.Perimetro);
      Console.WriteLine("El área de la circunferencia es {0}", c.Area);
      Console.WriteLine("Pulsa INTRO para incrementar el Radio en 1");
      string a = Console.ReadLine();

      c.Radio++;
      Console.WriteLine("El radio de la circunferencia es {0}",c.Radio);
      Console.WriteLine("El perímetro de la circunferencia es {0}",
    c.Perimetro);
      Console.WriteLine("El área de la circunferencia es {0}", c.Area);

      a=Console.ReadLine();
}

Se accede a las propiedades tal y como se accedería a los datos de la clase a definida con campos. Sin embargo se obtiene un control absoluto sobre los datos de la clase gracias a las propiedades. Es posible modificar el valor del Radio con toda naturalidad (en la línea c.Radio++) y esta modificación afecta también a las propiedades Perimetro y Area. Cuando se instancia el objeto, se ejecuta su constructor, asignándole el valor que se pasa como argumento al campo radio (que es protected y, por lo tanto, no accesible desde el cliente). Cuando se recupera el valor de la propiedad Radio para escribirlo en la consola se ejecuta el bloque "get" de dicha propiedad, y este bloque devuelve, el valor del campo radio, que era la variable donde se almacenaba este dato. Cuando se recuperan los valores de las otras dos propiedades también para escribirlos en la consola sucede lo mismo, se ejecutan los bloques get de cada una de ellas que, retornan el resultado de calcular dichos datos. Por último, cuando se incrementa el valor del radio (c.Radio++) lo que se ejecuta es el bloque set de la propiedad, se asigna el nuevo valor (representado por "value") a la variable protected radio. 

Las propiedades Area y Perimetro no tienen bloque set, son propiedades de sólo lectura. La diferencia de una propiedad con un campos de sólo lectura es que  un campo de sólo lectura ha de estar representado necesariamente por una variable, y, además, solamente se le puede asignar el valor una vez en el constructor; por contra, el que una propiedad sea de sólo lectura no implica que su valor sea constante, sino única y exclusivamente que no puede ser modificado por el cliente. Si se hubiéran puesto campos de sólo lectura no sería posible modificarlos  ni por el cliente, ni por la propia clase. Value es una variable que  declara y asigna implícitamente el compilador en un bloque set para que  sepamos cuál es el valor que el cliente quiere asignar a la propiedad, si se escribe c.Radio=8, value valdría 8. De este modo podremos comprobar si el valor que se intenta asignar a la propiedad es adecuado. 

Las propiedades funcionan internamente como si fueran métodos, pero no es necesario poner los paréntesis cuando son invocadas, pues se accede a ellas como si fueran campos. Sin embargo esto no quiere decir que siempre sea mejor escribir propiedades en lugar de métodos. Las propiedades se utilizan para hacer un uso más natural de los objetos. Hay que escribir un método cuando este implique una acción, y una propiedad cuando esta implique un dato. 

No hay comentarios:

Publicar un comentario