Языки программирования

Основы ООП в языке C#

В статье «Основы ООП» мы уже рассказывали что такое объектно-ориентированное программирование. Если вы новичок, тогда советуем изучить эту статью. В этой же статье пойдет разговор об основах ООП в языке C#.

Класс

Класс можно считать шаблоном, по которому определяется форма объекта. В этом шаблоне указываются данные и код, который будет работать с этими данными – поля и методы.
В C# используется спецификация класса для построения объектов, которые являются экземплярами класса.
Для объявления класса используется ключевое слово class:
class имя_класса {
    // Объявление переменных экземпляра.
    access type var1; // задает тип доступа, например, public
    access type var2;
    //…
    access type varN;

    // Объявление методов.
    access return_type method1 ([args]) {
          // тело метода
     }
     access return_type method2 ([args]) {
           // тело метода
     }
//….
access return_type method([args]) {
          // тело метода
     }
}

Конструкторы

Основная задача конструктора – инициализировать объект при его создании. Общая форма определения конструктора:
access имя_класса(args) {
 // тело цикла 
} 
Спецификатор доступа (access) обычно указывается public, поскольку конструкторы зачастую вызываются в классе, а вот список параметров args может быть ка пустым, так и состоящим из одного или нескольких параметров. Имя конструктора должно совпадать с именем класса.

Конструктор может принимать один или несколько параметров. В конструктор параметры вводятся таким же образом, как и в метод. Для этого достаточно объявить их в скобках после имени конструктора. Пример применения конструктора:
class Human
        {
            public string Name;
            public byte Age;

            // Устанавливаем параметры
            public Human(string n, byte a)
            {
                Name = n;
                Age = a;
            }

            public void GetInfo()
            {

                Console.WriteLine("Name: {0}\nAge: {1}", Name, Age);
            }

            // Создаем объект
            class Program
            {
                static void Main(string[] args)
                {
                    Human ex = new Human("Viktor", 25);
                    ex.GetInfo();

                    Console.ReadLine();
                }
            }
        }

Деструкторы

В языке C# имеется возможность определить метод, который будет вызываться непосредственно перед окончательным уничтожением объекта системой «сборки мусора». Такой метод называется деструктором и может использоваться в ряде особых случаев, чтобы гарантировать четкое окончание срока действия объекта.
Например, деструктор может быть использован для гарантированного освобождения системного ресурса, задействованного освобождаемым объектом.
Синтаксис определения деструктора такой же, как и конструктора, но именем класса указывается ~:
access ~имя_класса(args) {
 // тело конструктора
}
Обычно деструкторы не нужны, поскольку все используемые объектом ресурсы автоматически освобождаются при его уничтожении.

Пример использования деструкторов:
 class Human
    {
        int k;

        public Human(int i)
        {
            k = i;
        }

        // Деструктор
        ~Human()
        {
            Console.WriteLine("Объект {0} уничтожен", k);
        }

        // Метод создающий и тут же уничтожающий объект
        public void objectGenerator(int i)
        {
            Human ob = new Human(i);
        }
    }
 class Program
    {
        static void Main()
        {
            int i = 1;
            Human obj = new Human(0);

            for (; i < 120000; i++)
            {
                obj.objectGenerator(i);
            }

            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("\nКонец");

            Console.ReadLine();
        }
    }

Ключевое слово this

В языке C# имеется ключевое слово this, которое обеспечивает доступ к текущему экземпляру класса. Одно из возможных применений ключевого слова this состоит в том, чтобы разрешать неоднозначность контекста, которая может возникнуть, когда входящий параметр назван так же, как поле данных данного типа.
Конечно, чтобы такой неоднозначности не было, лучше придерживаться правил именования переменных, параметров, полей и т.д.
Чтобы продемонстрировать наглядное применение ключевого слова this, давайте рассмотрим следующий пример:
 class Human
    {
        public char ch;

        // 2 метода использующие входной параметр ch, при
        // этом во втором методе используется ключевое слово this
        public void Method1(char ch)
        {
            ch = ch;
        }

        public void Method2(char ch)
        {
            this.ch = ch;
        }
    }
 class Program
    {
        static void Main()
        {
            char myCH = 'A';
            Console.WriteLine("Исходный символ {0}", myCH);

            Human obj = new Human();

            obj.Method1(myCH);
            Console.WriteLine("Использование метода без ключевого слова this: {0}", obj.ch);
            obj.Method2(myCH);
            Console.WriteLine("Использование метода c ключевым словом this: {0}", obj.ch);
            Console.ReadLine();
        }
    }

Доступ к членам класса

Существует два типа членов класса – публичные (public) и приватные (private). Доступ к открытому члену возможен из кода, существующего за пределами класса. А доступ к приватному члену возможен только методам, которые определены в самом классе. Приватные члены позволяют организовать управление доступом.
Кроме известных спецификаторов (модификаторов) доступа public и private, в C# поддерживаются еще два модификатора – protected и internal.
Спецификатор protected означает член класса, доступ к которому открыт в пределах иерархии классов. Спецификатор internal используется в основном для сборок и начинающим программистам он не нужен.

Модификаторы параметров

Модификатор out

Модификатор служит только для передачи значения за пределы метода. Поэтому переменной, используемой в качестве параметра out, не нужно (да и бесполезно) присваивать какое-то значение. Более того, в методе параметр out считается неинициализированным, т.е. предполагается, что у него отсутствует первоначальное значение.
Это означает, что значение должно быть присвоено данному параметру в методе до его завершения. Следовательно, после вызова метода параметр out будет содержать некоторое значение. Рассмотрим пример:
 class Program
    {
        // Метод возвращающий целую и дробную части
        // числа, квадрат и корень числа
        static int TrNumber(double d, out double dr, out double sqr, out double sqrt)
        {
            int i = (int)d;
            dr = d - i;
            sqr = d * d;
            sqrt = Math.Sqrt(d);

            return i;
        }

        static void Main()
        {
            int i;
            double myDr, mySqr, mySqrt, myD = 13.987;
            i = TrNumber(myD, out myDr, out mySqr, out mySqrt);

            Console.WriteLine("Исходное число: {0}\nЦелая часть числа: {1}\nДробная часть числа: {2}\nКвадрат числа: {3}\nКвадратный корень числа: {4}", myD, i, myDr, mySqr, mySqrt);

            Console.ReadLine();
        }
    }

Модификатор ref

Модификатор параметра ref принудительно организует вызов по ссылке, а не по значению. Этот модификатор указывается как при объявлении, так и при вызове метода.
Параметры, сопровождаемые таким модификатором, называются ссылочными и применяются, когда нужно позволить методу выполнять операции и обычно также изменять значения различных элементов данных, объявляемых в вызывающем коде (например, в процедуре сортировки или обмена).

Модификатор params

В C# поддерживается использование массивов параметров за счет применения ключевого слова params. Ключевое слово params позволяет передавать методу переменное количество аргументов одного типа в виде единственного логического параметра.
Аргументы, помеченные ключевым словом params, могут обрабатываться, если вызывающий код на их месте передает строго типизированный массив или разделенный запятыми список элементов.

Рекурсия

В C# допускается, чтобы метод вызывал самого себя. Этот процесс называется рекурсией, а метод, вызывающий самого себя, — рекурсивным. Вообще, рекурсия представляет собой процесс, в ходе которого нечто определяет само себя.
Рекурсивный метод отличается главным образом тем, что он содержит оператор, в котором этот метод вызывает самого себя. Рекурсия является эффективным механизмом управления программой.
Классическим примером рекурсии служит вычисление факториала числа:
class Program
    {
        // Рекурсивный метод
        static int factorial(int i)
        {
            int result;

            if (i == 1)
                return 1;
            result = factorial(i - 1) * i;
            return result;
        }
 static void Main(string[] args)
        {
        label1:
            Console.WriteLine("Введите число: ");
            try
            {
                int i = int.Parse(Console.ReadLine());
                Console.WriteLine("{0}! = {1}", i, factorial(i));
            }
            catch (FormatException)
            {
                Console.WriteLine("Некорректное число");
                goto label1;
            }

            Console.ReadLine();
        }
    }
Главное преимущество рекурсии заключается в том, что она позволяет реализовать некоторые алгоритмы яснее и проще, чем итерационным способом.

Ключевое слово static

Если член класса объявляется как static, то он становится доступным до создания любых объектов своего класса и без ссылки на какой-нибудь объект. С помощью ключевого слова static можно объявлять как переменные, так и методы.
Наиболее характерным примером члена типа static служит метод Main(), который объявляется таковым потому, что он должен вызываться операционной системой в самом начале выполняемой программы.
Для того чтобы воспользоваться членом типа static за пределами класса, достаточно указать имя этого класса с оператором-точкой. Но создавать объект для этого не нужно. В действительности член типа static оказывается доступным не по ссылке на объект, а по имени своего класса.
Переменные, объявляемые как static, являются глобальными. Когда же объекты объявляются в своем классе, то копия переменной типа static не создается. Вместо этого все экземпляры класса совместно пользуются одной и той же переменной типа static.

Индексаторы

В C# имеется возможность проектировать специальные классы и структуры, которые могут быть индексированы подобно стандартному массиву, посредством определения индексатора.
Это конкретное языковое средство наиболее полезно при создании специальных типов коллекций (обобщенных и необобщенных). Индексаторы могут быть одно- или многомерными. Многомерные используются редко.

Одномерные индексаторы

 тип_элемента this[int индекс]
    {
        // Аксессор для получения данных,
        get
        {
            // Возврат значения, которое определяет индекс.
        }
        // Аксессор для установки данных,
        set
        {
            // Установка значения, которое определяет индекс.
        }
    }
1. тип_элемента обозначает конкретный тип элемента индексатора. Следовательно, у каждого элемента, доступного с помощью индексатора, должен быть определенный тип_элемента. Этот тип соответствует типу элемента массива.

2. Параметр индекс получает конкретный индекс элемента, к которому осуществляется доступ. Формально этот параметр совсем не обязательно должен иметь тип int, но поскольку индексаторы, как правило, применяются для индексирования массивов, то чаще всего используется целочисленный тип данного параметра.

Рассмотрим пример:
class MyArr
    {
        int[] arr;
        public int Length;

        public MyArr(int Size)
        {
            arr = new int[Size];
            Length = Size;
        }

        // Создаем простейший индексатор
        public int this[int index]
        {
            set
            {
                arr[index] = value;
            }

            get
            {
                return arr[index];
            }
        }
    }
 class Program
    {
        static void Main()
        {
            MyArr arr1 = new MyArr(Size: 5);
            Random ran = new Random();

            // Инициализируем каждый индекс экземпляра класса arr1
            for (int i = 0; i < arr1.Length; i++)
            {
                arr1[i] = ran.Next(1, 100);
                Console.Write("{0}\t", arr1[i]);
            }

            Console.ReadLine();
        }
    }

Свойства

Свойства очень похожи на индексаторы. В частности, свойство состоит из имени и аксессоров get и set. Аксессоры служат для получения и установки значения переменной.
Главное преимущество свойства заключается в том, что его имя может быть использовано в выражениях и операторах присваивания аналогично имени обычной переменной, но в действительности при обращении к свойству по имени автоматически вызываются его аксессоры get и set. Синтаксис:
 тип имя
    {
        get
        {
            // код аксессора для чтения из поля
        }

        set
        {
            // код аксессора для записи в поле
        }
    }
где тип обозначает конкретный тип свойства, например int, а имя — присваиваемое свойству имя. Как только свойство будет определено, любое обращение к свойству по имени приведет к автоматическому вызову соответствующего аксессора. Кроме того, аксессор set принимает неявный параметр value, который содержит значение, присваиваемое свойству.
Самоучитель по C#