C#. Свойства. Аксессоры get, set. Примеры классов, содержащих свойства

C#. Свойства. Аксессоры get, set. Примеры классов, содержащих свойства


Содержание



1. Понятие свойства. Общая форма. Аксессоры get, set

Свойство – это специальное поле класса, содержащее программный код доступа к внутренним переменным (полям) класса или вычисления некоторого значения. Свойства обеспечивают удобный и рациональный путь доступа к внутренней переменной в классе.

Общая форма объявления свойства в классе

type PropName {
    get {
        // код для чтения внутреннего поля (переменной)
        // ...
    }
    set {
        // код для записи значения во внутреннее поле (переменную)
        // ...
    }
}

где

  • type – тип данных возвращаемый свойством;
  • PropName – имя свойства. По этому имени идет обращение к свойству;
  • get – аксессор, который используется для чтения значения из внутреннего поля класса;
  • set – аксессор, используемый для записи значения во внутреннее поле класса. Аксессор set получает неявный параметр value, содержащий значение, которое присваивается свойству.

Если имя свойства встречается в правой части оператора присваивания или в выражении

variable = obj.PropName; // get

тогда вызывается get. Здесь obj – имя экземпляра класса, в котором объявлено свойство PropName.

Если имя свойства встречается в левой части оператора присваивания

obj.PropName = expression; // set

тогда вызывается аксессор set. Здесь obj – имя экземпляра класса, в котором объявлено свойство PropName.

 

2. Преимущества использования свойств в классах

Использование свойств в классах дает следующие преимущества:

  • обеспечивается более рациональный и естественной доступ к внутренним переменным класса;
  • в коде аксессоров можно реализовывать разного рода проверки на допустимость значений внутренних переменных класса;
  • гибкость в правах доступа к внутренней переменной: отсутствие аксесора set позволяет реализовать доступ «только для чтения» для внутренних данных класса. Это, в свою очередь, делает невозможным случайное изменение этих данных.

 

3. Пример свойства, реализующего чтение/запись переменной типа int

В примере демонстрируется простой случай доступа к свойству в классе. Объявляется класс с именем IntNumber в котором реализованы:

  • внутренняя скрытая (private) переменная типа int с именем d;
  • конструктор с одним параметром, который инициализирует переменную d;
  • свойство с именем Number. Это свойство содержит аксессоры get и set;
  • метод Display для вывода значения скрытой (private) переменной d.

Текст класса IntNumber и его использование в функции main() следующий (приложение типа Console Application)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp7
{
  // простейший пример класса, в котором используются свойства
  class IntNumber
  {
    private int d;

    // конструктор
    public IntNumber(int _d)
    {
      d = _d;
    }

    // объявление свойства с именем Number
    private int Number
    {
      // чтение поля d
      get
      {
        return d;
      }

      // запись в поле (переменную) d
      set
      {
          d = value;
      }
    }

    // вывод поля d на экран
    public void Display()
    {
      Console.WriteLine("d = {0}", d);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // объявить экземпляр с именем number класса IntNumber
      IntNumber number = new IntNumber(5);

      // вывести значение внутренней переменной d
      number.Display(); // d = 5

      // изменить значение d через свойство Number.set
      number.Number = 25;
      number.Display(); // d = 25

      // использовать свойство Number.get для чтения d
      int t;
      t = number.Number; // t = 25
      Console.WriteLine("t = {0}", t);
    }
  }
}

Результат выполнения программы

d = 5
d = 25
t = 25
Press any key to continue . . .

 

4. Как влияют спецификаторы доступа к элементам класса (private, public) на доступ к свойству?

Чтобы использовать свойство, оно должно быть объявлено в разделе public.

Если свойство объявить со спецификатором доступа private, то это свойство будет недоступно. В результате, при попытке доступа к свойству, компилятор выдаст сообщение об ошибке

ClassName.PropName is inaccessible due to its protection level

где PropName – имя private-свойства, объявленного в классе ClassName.

 

5. Пример свойства, которое реализует доступ к внутренней переменной, которая есть массивом чисел типа double

В примере объявляется класс ArrayDouble, реализующий массив вещественных чисел. В классе объявляются:

  • внутренняя скрытая (private) переменная A, которая есть массивом чисел типа double;
  • два конструктора класса;
  • индексатор, который возвращает элемент массива по заданному индексу;
  • свойство Sum, которое определяет аксессоры get и set.

Текст класса ArrayDouble и его использование в методе main(), следующий

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp7
{
  // массив типа double
  class ArrayDouble
  {
    double[] A; // массив чисел типа double

    // конструкторы класса
    public ArrayDouble(int size)
    {
      // выделить память под массив
      A = new double[size];

      // заполнить массив нулями
      for (int i = 0; i < size; i++)
        A[i] = 0.0;
    }

    public ArrayDouble(double[] _A)
    {
      // выделить память для массива
      A = new double[_A.Length];

      // скопировать один массив в другой
      for (int i = 0; i < _A.Length; i++)
        A[i] = _A[i];
    }

    // индексатор
    public double this[int index]
    {
      get
      {
        if ((index >= 0) && (index < A.Length))
          return A[index];
        else
          return 0.0;
      }
    }

    // свойство Sum:
    // get - определяет сумму элементов массива
    // set - распределяет элементы в массиве равными значениями,
    // сумма элементов равна value
    public double Sum
    {
      get
      {
        // вычисление суммы элементов
        double s = 0;
        for (int i = 0; i < A.Length; i++)
          s += A[i];
        return s;
      }
      set
      {
        // равномерное распределение элементов массива A значениями value
        double v = value/A.Length;
        for (int i = 0; i < A.Length; i++)
          A[i] = v;
      }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      double[] X = { 1.0, 2.5, 2.0, 4.5, 0.0 }; // массив
      ArrayDouble ad = new ArrayDouble(X); // создать экземпляр класса ArrayDouble

      double sum; // сумма элементов
      sum = ad.Sum;
      Console.WriteLine("sum = {0}", sum);

      // заполнить массив в экземпляре ad значениями 1.0
      ad.Sum = 1.0 * X.Length;

      // проверка
      double t;
      t = ad[2]; // t = 1.0
      Console.WriteLine("t = {0}", t);
    }
  }
}

В классе ArrayDouble реализовано свойство Sum. Это свойство определяет два аксессора get и set. С целью демонстрации аксессор get реализует вычисление суммы элементов массива A

...
public double Sum
{
  get
  {
    // вычисление суммы элементов
    double s = 0;
    for (int i = 0; i < A.Length; i++)
      s += A[i];
    return s;
  }
  ...
}
...

Также с помощью аксессора set в каждый элемент массива A заносится значение value/A.length

...
set
{
  // равномерное распределение элементов массива A значением value
  double v = value/A.Length;
  for (int i = 0; i < A.Length; i++)
    A[i] = v;
}
...

Вывод: в аксессорах get, set можно задавать дополнительные операции вычисления над данными класса. Необязательно должно возвращаться значение некоторого поля в классе.

Результат работы программы

sum = 10
t = 1
Press any key to continue . . .

 

6. Пример свойства, которое реализует доступ к структурной переменной (struct)

В примере демонстрируется доступ к структуре типа Fracion с помощью свойств. Свойства объявляются в классе Complex, реализующем комплексное число. Класс Complex декларирует следующие элементы:

  • внутренние скрытые (private) переменные real, imag. Эти переменные (поля) соответствуют действительной и мнимой части комплексного числа;
  • свойство Real для чтения/записи значения поля real;
  • свойство Imag для чтения/записи значения поля imag;
  • свойство RealD, представляющее значение поля real в виде вещественного числа типа double. Это свойство предназначено только для чтения (get), аксесор set отсутствует в свойстве;
  • свойство ImagD, представляющее значение поля imag в виде вещественного числа типа double. Это свойство предназначено только для чтения (get);
  • свойство Abs, вычисляющее модуль комплексного числа. Это свойство предназначено только для чтения.

Текст программы для приложения типа Console Application следующий

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp7
{
  // структура, реализующая дробь
  struct Fraction
  {
    public int num; // числитель
    public int denom; // знаменатель
  }

  // клас, реализующий комплексное число
  class Complex
  {
    private Fraction real; // действительная часть комплексного числа в виде дроби
    private Fraction imag; // мнимая часть комплексного числа в виде дроби

    // конструкторы
    public Complex(int _real, int _imag)
    {
      real.num = _real;
      real.denom = 1;
      imag.num = _imag;
      imag.denom = 1;
    }

    public Complex(Fraction _real, Fraction _imag)
    {
      real = _real;
      imag = _imag;
    }

    // свойство, соответствующее действительной части комплексного числа
    public Fraction Real
    {
      get
      {
        return real;
      }
      set
      {
        real = value;
      }
    }

    // свойство, соответствующее мнимой части комплексного числа
    public Fraction Imag
    {
      get
      {
        return imag;
      }
      set
      {
        imag = value;
      }
    }

    // свойство, возвращающее действительную часть комплексного числа
    // в виде типа double
    public double RealD
    {
      get
      {
         return (double)real.num / (double)real.denom;
      }
    }

    // свойство, возвращающее мнимую часть комплексного числа
    // в виде типа double
    public double ImagD
    {
      get
      {
        return (double)imag.num / (double)imag.denom;
      }
    }

    // свойство, возвращающее модуль комплексного числа
    public double Abs
    {
      get
      {
        double t,r,i;
        r = real.num / (double)real.denom;
        i = imag.num / (double)imag.denom;
        t = Math.Sqrt(r * r + i * i);
        return t;
      }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      Fraction r = new Fraction();
      Fraction i = new Fraction();
      r.num = 3;
      r.denom = 1;
      i.num = 4;
      i.denom = 1;

      Complex cm = new Complex(r, i); // создать экземпляр класса Complex

      // демонстрация работы свойств класса Complex
      // свойство Abs
      double abs = cm.Abs; // взять модуль, abs = 5
      Console.WriteLine("Abs = {0}", abs);

      // свойства RealD, RealI
      double rd, id;
      rd = cm.RealD; // rd = 3/1 = 3.0
      id = cm.ImagD; // id = 4/1 = 4.0

      Console.WriteLine("rd = {0}", rd);

      // свойство Real
      Fraction R = new Fraction();
      R = cm.Real; // R = { 3, 1 }
      Console.Write("R.num = {0},   ", R.num);
      Console.WriteLine("R.denom = {0}", R.denom);
    }
  }
}

Результат работы программы

Abs = 5
rd = 3
R.num = 3, R.denom = 1
Press any key to continue . . .

 

7. Пример свойства, которое реализует доступ к объекту класса

В примере демонстрируется доступ к объекту класса с помощью свойства.

Объявляется класс Point, реализуючий точку на координатной плоскости. В классе объявляются:

  • две внутренние скрытые (private) переменные x, y которые есть координатами точки;
  • два конструктора;
  • свойства X, Y. Эти свойства с помощью аксессоров get, set реализуют доступ к скрытым переменным x, y.

Также объявляется класс Line, который реализует линию состоящую из двух точек типа Point. В классе реализованы:

  • скрытые (private) внутренние переменные p1, p2 типа Point. Это точки концов отрезка;
  • свойства P1, P2 реализующие доступ к внутренним переменным p1, p2. В этих свойствах продемонстрирован возврат объекта класса типа Point;
  • свойство Length, определяющее длину линии. Это свойство содержит только один аксессор get. Аксессор set отсутствует.

Текст демонстрационной программы, созданной по шаблону Console Application следующий

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp7
{
  // класс, реализующий линию
  class Point
  {
    private float x;
    private float y;

    // конструкторы
    public Point()
    {
      x = y = 0f;
    }

    public Point(float _x, float _y)
    {
      x = _x;
      y = _y;
    }

    // свойства
    public float X
    {
      get { return x; }
      set { x = value; }
    }

    public float Y
    {
      get { return y; }
      set { y = value; }
    }
  }

  // класс, реализующий линию
  class Line
  {
    // внутренние данные - точки линии, объекты класса Point
    private Point p1;
    private Point p2;

    // конструктор
    public Line(Point _p1, Point _p2)
    {
      p1 = new Point();
      p1.X = _p1.X;
      p1.Y = _p1.Y;
      p2 = new Point(_p2.X, _p2.Y);
    }

    // свойства
    public Point P1 // получить первую точку линии
    {
      get { return p1; }
      set { p1 = value; }
    }

    public Point P2 // получить вторую точку линии
    {
      get { return p2; }
      set { p2 = value; }
    }

    // свойство, определяющее длину линии,
    // содержит только один аксессор get
    public double Length
    {
      get
      {
        double len;
        len = Math.Sqrt(p1.X * p1.X + p2.Y * p2.Y);
        return len;
      }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // доступ к объекту класса с помощью свойства
      // создать экземпляры класса
      Point p1 = new Point(2, 4);
      Point p2 = new Point(6, 8);
      Line line = new Line(p1, p2);

      // демонстрация свойств P1, P2 класса Line
      Point p3, p4;
      p3 = line.P1; // аксессор get
      p4 = line.P2;
      float x, y;
      x = p3.X; // x = 2
      y = p3.Y; // y = 4
      Console.WriteLine("x = {0},   y = {1}", x, y);

      p3.X = -3;
      p3.Y = -1;
      line.P1 = p3; // аксессор set
      Console.WriteLine("x = {0},   y = {1}", line.P1.X, line.P1.Y);

      // вывести длину линии с помощью свойства Length
      double length;
      length = line.Length;
      Console.WriteLine("Length = {0}", length);
    }
  }
}

Как видно из вышеприведенного кода, в классе Line реализованы два свойства, которые осуществляют доступ к объекту класса Point

...
// свойства
public Point P1 // получить первую точку линии
{
  get { return p1; }
  set { p1 = value; }
}

public Point P2 // получить вторую точку линии
{
  get { return p2; }
  set { p2 = value; }
}
...

Результат выполнения программы

x = 2, y = 4
x = -3, y = -1
Length = 8.54400374531753
Press any key to continue . . .

 

8. Определяют ли свойства место в памяти для полей класса?

Нет, не определяют. Свойства руководят доступом к полям. Само свойство не представляет поле (член данных) класса. Поле должно быть определено в классе независимо от свойства.

 


Связанные темы