Живые и Твёрдые Издательский Дом Коммерсантъ

Создание класса

Класс создаётся путём написания кода с использованием ключёвого слова class. Class объявляется внутри какого-либо пространства имен (namespace), что гарантирует его уникальность в программном проекте.
Для примера объявим (создадим) класс с именем Vertex3d. Пока класс не несёт в себе никакой функциональности. Он пуст. В дальнейшем будем его использовать для хранения и обработки пространственных координат точки. Положение точки в пространстве задаётся тремя координатами.
    // default namespaces to import, that Visual Studio includes in each file
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    namespace ClassSample
    {
        // public so that it’s visible outside of assembly
        // ключевое слово public позволяет классу быть видимым за пределами сборки кода
        
        public class Vertex3d
        {

        }
    }
Для справки напомним модификаторы доступа в С#
Таблица 1.1. Модификаторы доступа
ДоступностьПредназначен дляОписание
PublicТипы, члены классаДоступен всем, даже из вне сборки
PrivateТипы, члены классаДоступен в коде того типа (класса, структуры,функции), где объявлен
InternalТипы, члены классаДоступен в коде сборки где объявлен
ProtectedЧлены классаДоступен в коде того типа, где объявлен и в порождённых от них объектах
Protected internalЧлены классаДоступен в коде коде того типа, где объявлен или в порождённых от них объектах в других сборках

Определение полей, свойств и методов

Как добавить поле, свойство и метод в класс/в описание класса

Сделаем это на примере того-же, всё еще пустого, класса Vertex3d
    public class Vertex3d
    {
        // fields | Поля 
        private double _x;
        private double _y;
        private double _z;
        
        // properties | свойства 
        public double X
        {
            get { return _x; }
            set { _x = value; }
        }
        public double Y
        {
            get { return _y; }
            set { _y = value; }
        }
        public double Z
        {
            get { return _z; }
            set { _z = value; }
        }
        
        // method | метод 
        public void SetToOrigin()
        {
            X = Y = Z = 0.0;
        }
    }
Некоторые замечания по вышеприведённому коду:
  • Все поля объявлены как private, что является хорошим тоном программирования
  • Свойства же объявлены как public, но могут быть и private, protected, или protected internal, зависит от намерений разработчика
  • Свойства имеют определения для get, set, или оба сразу
  • В свойствах, value - это значение передаваемого аргумента (то, что будет написано в коде)
    // в нижеприведённом примере, 13.0 будет присвоено свойству X через значение аргумента value:
    Vertex3d v = new Vertex3d();
    v.X = 13.0;

Автоматически-реализуемые свойства

В своей программистской практике часто встречается такой приём определения свойства
    class MyClass
    {
        private int _field = 0;
        public int Field { get { return _field; } set { _field = value; } }
    }
Чтобы явно не объявлять _field в C# можно использовать укороченный синтаксис для такой конструкции:
    class MyClass
    {
        public int Field {get; set;}
        // значение должно быть инициализированно в конструкторе
        public MyClass()
        {
            this.Field = 0;
        }
    }
Заметим, что вы не сможете создать Автоматически-реализуемое свойство только с get определением (так как без set не создаётся автоматическое поле для хранения значения свойства и, в таком случае нет возможности вернуть какое либо значение через get)...
Но, можно задать модификатор private для set, что будет равносильно созданию свойства (Field) только с get определением:
    public int Field { get ; private set; } 
 

Определение статических членов класса

Вы хотите определить таким образом данные или методы, чтобы они были присущи всему классу (общие для всего класса), а не для его отдельной реализации. Имея ввиду, что этими данными или методами будут часто пользоваться члены класса, которые принадлежат отдельной реализации этого класса.
Чтобы добиться такого эффекта добавим к определению ключевое слово-модификатор static:
    public class Vertex3d
    {
        ...
        public static Vertex3d Add(Vertex3d a, Vertex3d b)
        {
            Vertex3d result = new Vertex3d();
            result.X = a.X + b.X;
            result.Y = a.Y + b.Y;
            result.Z = a.Z + b.Z;
            return result;
        }
    }
 
То есть метод Add класса Vertex3d можно вызывать не создавая экземпляр класса.
     Vertex3d a = new Vertex3d(0,0,1);
     Vertex3d b = new Vertex3d(1,0,1);
     Vertex3d sum = Vertex3d.Add(a, b);
 

Конструирование Конструкторов

Добавим конструктор

Обычно конструктор используется для автоматизации инициализации новых объектов класса.
Конструктор - это специальный метод, с тем же именем, что и имя класса и без возвращаемого типа; Конструктор вызывается при создании класса и никогда не может быть вызван напрямую в коде.
Вот два конструктора для класса Vertex3d - один с параметрами, другой без параметров, для использования инициализации по умочанию.
    class Vertex3d
    {
        public Vertex3d()
        {
            _x = _y = _z = 0.0;
        }
        public Vertex3d(double x, double y, double z)
        {
            this._x =x;
            this._y = y;
            this._z = z;
        }
    }
Конструкторам нет необходимости задавать модификатор public. Например, вы можете объявить конструктор с модификатором protected и тогда он будет доступен только классу наследнику. Вы можете даже задать модификатор private для предотвращения инициализации (для классов утилит) или доступа только из методов только того же класса ( скажем статических методов )

Конструктор static и инициализация

Необходимо инициализировать переменные, отмеченные в классе, как static
Поля (переменные), отмеченные в классе, как static, могут быть инициализированны двумя способами. Первый - инициализация в конструкторе, имеющем в объявлении static , который, по сути, есть стандартный static конструктор, но без модификатора доступа или параметров:
    public class Vertex3d
    {
        private static int _numInstances;
        static Vertex3d()
        {
            _numInstances = 0;
        }
        ...
    }
 
Второй способ - инициализация при объявлении поля (переменной), как показано ниже:
    public class Vertex3d
    {
        private static int _numInstances = 0;
        ...
    }

Инициализация свойств в конструкторах

Вы хотите инициализировать свойства класса когда переменная создаётся, даже если конструктор не позволяет передать аргументы для выполнения этого.
Возпользуйтесь синтаксисом инициализации объектов, как в нижеследущем примере:
    class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
    }
    ...
    Person p = new Person()
    { Id = 1, Name = “Ben”, Address = “Redmond, WA” };

Использование const и readonly

Вы хотите использовать поля (переменные) которые не должны изменяться во время исполнения программы.
Ключевые слова const и readonly используются для определения данных, которые в дальнейшем не изменяют своих значений.
Но имеются весьма важные различия:
  • поля const должны быть определены во время объявления.
    (это означает, что они принадлежат к типу статических полей)
  • поля readonly могут быть опраделены как во время объявления, так и конструкторе (и нигде больше)
    public class Vertex3d
    {
        private const string Name = “Vertex”;
        private readonly int ver;
        public Vertex3d()
        {
            ver = Config.MyVersionNumber; // Ok
        }
        public void SomeFunction()
        {
            ver = 13; // Ошибка!
        }
    }

Повторное использование кода при использовании нескольких конструкторов

У вашем классе несколько конструкторов и они имеют некоторый общий объём функциональности. При этом вы хотите избежать дублирования кода для упрощения его сопровождения. В С++ и в некоторых других, более ранних, языках можно вынести общий код в некую инициализующую функцию и вызывать её из каждого конструктора.
    // C++ example
    class MyCppClass
    {
    public:
        // конструктор без параметров
        MyCppClass() { Init(); }
        // конструктор с одним параметром
        MyCppClass(int arg) { Init(); }
        ....
        ....
    private:
        void Init() { /* общий код инициализации */ };
    }
В C# вы можете вызывать другие конструкторы, этого же класса, используя ключевое слово this:
    public class Segment2
    {
        private Point2 a;
        private Point2 b;
        private double lenght = 0;
        private bool changed = false;
        public Segment2()
        {
            this.a = new Point2();
            this.b = new Point2();
            this.a.x = this.a.y = this.b.x = this.b.y = 0;
            lenght = 0;
            changed = false;
        
        }
        public Segment2(Point2 a, Point2 b) : this()
        {
            this.a.x = a.x;
            this.a.y = a.y;
            this.b.x = b.x;
            this.b.y = b.y;
            changed = true;
            Len();
        }
        public double Len()
        {
            if (changed) lenght = Math.Sqrt(Math.Pow((b.x - a.x), 2) + Math.Pow((b.y - a.y), 2));

            return lenght;
        }
    }

    public class Vertex3d
    {
        private double _x, _y, _z;
        public Vertex3d()
        {
            _x = _y = _z = 0.0;
        }
        public Vertex3d(double x, double y, double z)
        {
            this._x = x;
            this._y = y;
            this._z = z;
        }
        public Vertex3d(System.Drawing.Point point)
            : this(point.X, point.Y, 0)
        {
        }
        ...
    }

Породиться от класса

Вы хотите создать свой класс, взяв за основу уже существующий класс (будем называть его базовым классом), добавляя свою функциональность и/или переопределяя поведение членов базового класса.
Воспользуемся наследованием свойств и добавим новую функциональность.
    public class BaseClass
    {
        private int _a;
        protected int _b;
        public int _c;
    }

    public class DerivedClass : BaseClass
    {
        public DerivedClass()
        {
            _a = 1; // ошибка! т.к переменная _a объявлена как private в BaseClass
            _b = 2; // ok
            _c = 3; // ok
        }
        public void DoSomething()
        {
            _c = _b = 99;
        }
    }
Из базового класса наследуются (являются доступными) только те свойства, которые имеют модификаторы доступа public и protected.

Вызов конструктора базового класса

У вас есть дочерний класс (тот, что порождёт от базового класса) и вы хотите в его конструкторе вызвать определённый конструктор базового класса.
Точно так-же, как вы имеете возможность вызвать, из конструктора класса, любой другой конструктор этого же класса, вы можете вызвать конструктор базового класса. Если вы не указываете конкретный конструктор, то будет вызван конструктор базового класса заданный по умолчанию. Если конструктор базового класса, определённый по умолчанию, требует аргументы, то вы должны их предавать ему.
Базовый класс - BaseClass
    public class BaseClass
    {
        public BaseClass(int x, int y, int z)
        { ... }
    }
Ваш класс - DerivedClass, порождённый от BaseClass
    public class DerivedClass : BaseClass
    {
        public DerivedClass()
        : base(1, 2, 3)
        {
        }
    }

Переопределение свойств и методов базового класса

Вы хотите изменить поведение класса, в своём порождённом классе, заданное базовым класом.
Изменить методы и свойства базового класса можно, если они доступны из класса наследника (public, protected) и отмечены ключевым словом virtual - т.е. свойство виртуально и может быть переопределено в классе наследнике.

Для переопределения методов и свойств в порождённом классе (классе наследнике) используется ключевое слово override.

    public class Base
    {
        Int32 _x;
        public virtual Int32 MyProperty
        {
            get
            {
                return _x;
            }
        }
        public virtual void DoSomething()
        {
            _x = 13;
        }
    }
  1. В базовом классе свойство MyProperty возвращает значение, хранящяеся в переменной _x.
    В классе наследнике изменим свойство MyProperty, так, чтобы оно возвращало значение, хранящяеся в переменной _x умноженное на 2.
  2. В базовом классе метод DoSomething() призваивает переменной _x значение 13.
    В классе наследнике изменим метод DoSomething(), так, чтобы он присваивал переменной _x значение 14.
    public class Derived : Base
    {
        public override Int32 MyProperty
        {
            get
            {
                // _x - определена в классе Base
                return _x * 2;
            }
        }
        public override void DoSomething()
        {
            // _x - определена в классе Base
            _x = 14;
        }
    }
Ссылки методов базового класса, при создании экземпляра класса , могут указывать как на реализацию свойств базового класса , так и на реализацию переопределённых свойств любого порожденного от него класса.

    Base d = new Base();
    d.DoSomething();
    // здесь будет напечатано "13"
    Console.WriteLine(d.MyProperty().ToString());
    
    Base d = new Derived();
    d.DoSomething();
    // здесь будет напечатано "28"
    Console.WriteLine(d.MyProperty().ToString());
Вы можете вызвать функцию базового класса из порождённого класса использую ключевое слово base.
    public class Base
    {
        public virtual void DoSomething()
        {
            Console.WriteLine(“Base.DoSomething”);
        }
    }
    public class Derived : Base
    {
        public override void DoSomething()
        {
            base.DoSomething();
            Console.WriteLine(“Derived.DoSomething”);
        }
    }
При вызове Derived.DoSomething() будет напечатано следущее:
    Base.DoSomething
    Derived.DoSomething

Переопределение невиртуальных методов и свойств

Функциональность (свойство или метод) базового класса не объявлена виртуальной (через ключевое слово virtual ) , но вам позарез надо переопределить её. Например в случаях когда вы порождаете свой класс от класса из библиотеки стороннего разработчика и вам надо то переопределить один метод, а он не отмечен в базовом классе ключевым словом virtual.
Что же, есть путь, НО предупрежу сразу: переопределённый таким образом метод будет доступен только из порождённого класса (ссылка на одноимённый метод в базовом классе не изменится). Чтобы сделать это, надо использовать ключевое слово new при объявлении метода. ( обычно это ключевое слово используется в другом контексте)
    class Base
    {
        public virtual void DoSomethingVirtual()
        {
            Console.WriteLine("Base.DoSomethingVirtual");
        }
        public void DoSomethingNonVirtual()
        {
            Console.WriteLine("Base.DoSomethingNonVirtual");
        }
    }
    // порождаем класс Derived от класса Base
    class Derived : Base
    {
        public override void DoSomethingVirtual()
        {
            Console.WriteLine("Derived.DoSomethingVirtual");
        }
        public new void DoSomethingNonVirtual()
        {
            Console.WriteLine("Derived.DoSomethingNonVirtual");
        }
    }
    // программа для тестирования поведения класса Derived
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(“Derived via Base reference:”);
            Base baseRef = new Derived();
            baseRef.DoSomethingVirtual();
            baseRef.DoSomethingNonVirtual();

            Console.WriteLine();
            Console.WriteLine(“Derived via Derived reference:”);
            Derived derivedRef = new Derived();
            derivedRef.DoSomethingVirtual();
            derivedRef.DoSomethingNonVirtual();
        }
    }
    // Результат работы теста
    Derived via Base reference:
    Derived.DoSomethingVirtual
    Base.DoSomethingNonVirtual
    Derived via Derived reference:
    Derived.DoSomethingVirtual
    Derived.DoSomethingNonVirtual
Ещё раз внимательно просмотрите результат работы теста. Убедитесь, что вам понятно, почему именно такие результаты были получены

Создание интерфейса

Вам требуется некакая абстрактная функциональность, без детальной проработки (без реализации кода), которой могут пользоваться порождённые классы, самостоятельно реализовывая её.
Вот, например, интерфейс некого объекта проигрывателя - возможно для аудио или видео файла, или даже потока данных. Интерфейс не определяет конкретной реализации в коде, но тем не менее описывает общее поведение объекта.
    public interface IPlayable
    {
        void Play();
        void Pause();
        void Stop();
        double CurrentTime { get; }
    }
Следует заметить, что интерфейсы могут содержать методы, свойства и события. К тому же, по умолчанию, они специфицируются модификатором public, который вам указавать не обязательно.

Реализация интерфейса

Вам надо, в вашем классе, реализовать интерфейс, определённый в базовом классе.
Чтобы реализовать интерфейс, вам требуется объявить в своём классе все методы и свойства реализуемого интерфейса, установить им модификатор доступа - public и написать код для всех методов и поддерживаемых вашим классом событий.
    // создаём класс от интерфейса IPlayable
    public class AudioFile : IPlayable
    {
        private IntAudioStream _stream;
        ...
        // пишем код для всех методов интерфейса
        public void Play()
        {
            ...
            _stream.Play();
        }
        public void Pause()
        {
            _stream.Stop();
        }
        public void Stop()
        {
            _stream.Stop();
            _stream.Reset();
        }
    }
Visual Studio может оказать вам в этом помощь. Когда вы набираете ": IPlayable" после имени класса, то Visual Studio высвечивает Smart Tag поверх названия интерфейса. Кликая на Smart Tag вы переходите к опциям и в одной из них можно выбрать создание пустой (без кода) реализации интерфейса в классе. Очень удобно, экономит время.

Реализация нескольких интерфейсов

В вашем простом классе требуется реализовать функциональность нескольких интерфейсов. И вероятно, они будут конфликтовать - у них могут совпадать наименования методов, свойств и/или событий.
Всё очень просто - при объявлении класса, после двоеточия, перечисляем имена интерфейсов через запятую.
    public class AudioFile : IPlayable, IRecordable
    {
        ...
    }
Тем не менее вы можете выполнять в своём классе любую реализацию, из совпадающих по именам методов, явно приводя их к типу соответствующего интерфейса.

В примере, ниже, описаны два интерфейса, в каждом из которых объявлен метод Stop(). В таком случае имя интерфейса задаётся явным образом.

    public class AudioFile : IPlayable, IRecordable
    {
        void Stop() // метод по умолчанию
        {
            // IPlayable interface - т.к. первый в списке объявления класса
        }
        void IRecordable.Stop()
        {
            // IRecordable interface
        }
    }

    // Здесь показано, как вызвать каждый из двух методов:

    // создаём экземпляр класса AudioFile - file
    AudioFile file = new AudioFile();
    // вызываем метод, реализованный для интерфейса IPlayable
    file.Stop();
    // вызоваем метод, реализованный для интерфейса IRecordable
    ((IRecordable)file).Stop();

Если же вы решите сделать по умочанию реализацию метода Stop() интерфейса IRecordable, то тогда, чтобы вызвать метод, Stop() реализованный от интерфейса IPlayable нужно будет указывать в коде ((IPlayable)file).Stop();, выполняя явное приведение к типу интерфейса.

Создание структуры

Вам надо создать некий объект с небольшим объёмом данных, но без накладных расходов, сопутствующих созданию класса
В отличии от С++, где структуры и классы функционально идентичны, в С# они имеют очень важные и фундаментальные различия:
  1. Подразумевается, что структуры (типы со значениями и, соответственно, их можно объявлять и без использования оператора new ), в отличии от ссылочных типов, всегда существуют в стеке данных. Они занимают меньше места в памяти (чем класс) и хорошо подходят для группировки небольшого объёма данных.
  2. Структуры не наследуются и не могут быть порождены от другой структуры.
  3. Структуры не могут иметь конструктора без параметра. Он всегда явно существует и инициализирует все поля нулевыми значениями.
  4. Все поля структуры должны инициализировать в каждом из конструкторов (сколько бы их не было).
  5. Структуры передаются в вызываемый метод по значению, как и любой тип со значением, какого бы большого размера структура не была.
Создание структуры похоже на создание класса:
    public struct Point
    {
        private Int32 _x;
        private Int32 _y;

        public Int32 X
        {
            get { return _x; }
            set { _x = value; }
        }
        public Int32 Y
        {
            get { return _y; }
            set { _y = value; }
        }
        public Point(int x, int y)
        {
            _x = x;
            _y = y;
        }
        
        public Point() {} // Так нельзя - конструктор без параметров существует неявно и объявлять его нельзя!
        
        // Так тоже нельзя, т.к. не инициализируется переменная _y (смотри выше п.4)
        public Point(int x) { this._x = x; }
    }
    
    // Пример использования :

    Point p; // объвили, что р это структура и пока не инициализированная
    Point p2 = new Point(); // создали (выделили память) и инициализировали p2, по умолчанию _x = _y = 0;
    
    p.X = 13; // пока нельзя, компилятор сообщает об использовании неинициализированной переменной 'p'
    
    p = p2; // присваивание структур - копируются все свойства
    // теперь под p выделена память
    p.X = 13; // и можно присваивать значение переменным структуры
    p.Y = 14;
    Console.WriteLine(p.X.ToString() + " " + p.Y.ToString());
    Console.WriteLine(p2.X.ToString() + " " + p2.Y.ToString());

Создание анонимных типов

Вам требуется, для одноразового использования, временный тип без названия
Ключевое слово var можно использовать для создания анонимных типов, которые содержат свойства, задаваемые в строке инициализации.
    class Program
    {
        static void Main(string[] args)
        {
            var part = new { ID = 1, Name = "Part01", Weight = 2.5 };
            //Prevent Instantiation with an Abstract Base Class 23
            Console.WriteLine("var Part, Weight: {0}", part.Weight);
            Console.WriteLine("var Part, ToString(): {0}", part.ToString());
            Console.WriteLine("var Part, Type: {0}", part.GetType());

        }
    }
    // Результат работы этой программы:
    var Part, Weight: 2.5
    var Part, ToString(): { ID = 1, Name = Part01, Weight = 2.5 }
    var Part, Type: <>f__AnonymousType0`3[System.Int32,System.String,System.Double]
Может показаться, что переменная, объявленная через var , нетипизирована (вообще не имеет никакого типа), однако это заблуждение. Компилятор самостоятельно создаёт строго типизированный объект под переменную, объявляемую через var.
    // простой пример:
    var type1     = new { ID = 1, Name = "A" };
    var type1Same = new { ID = 2, Name = "B" };
    var type2     = new { ID = 3, Name = "C", Age = 13 };
    type1 = type1Same; // Ok
    type1 = type2;     // Not ok
    // в строчке выше компилятор сообщит об ошибке:
    // Cannot implicitly convert type ‘AnonymousType#2’ to ‘AnonymousType#1’

Предотвращение создания объектов от абстрактного класса (запрет на реализацию)

Вы хотите создать бызовый класс с общей функциональностью, от которого будут, произходить другие классы, но не хотите, чтобы непосредственно от базового класса можно было создать объект.
В таком случае используйте ключевое слова abstract.
    public abstract class MyClass
    {
        ...
    }
    // порождаем класс MyDerivedClass от MyClass
    public class MyDerivedClass : MyClass
    {
        ...
    }
    MyClass myClass = new MyClass(); // Нельзя!
    MyClass myClass = new MyDerivedClass(); // ok
Вы можете маркировать отдельные методы внутри класса как абстрактные для предотвращения использования их по умолчанию в реализации класса, как показано ниже:
    public class MyClass
    {
        public abstract void DoSomething();
    }
    public class MyDerivedClass : MyClass
    {
        ...
    }
    MyClass myClass = new MyDerivedClass(); // ok
    myClass.DoSomething();  // Нельзя!

Что предпочесть: Интерфейс или Абстрактный класс?

При разработке иерархии классов, вам часто придётся сталкиваться с проблемой выбора: сделать ли родительский класс (корневой) абстрактным классом или же реализовывать его концепцию через интерфейсы.
Вот несколько соображений в помощь вам для формирования решения.
В пользу интерфейсов
  • Возможна реализация класса от наскольких интерфейсов. А вот реализовать класс от нескольких базовых классов в C# не возможно.
  • Есть ли у вас чёткое понимание того, что такое есть ваш класс и что он делает? Интерфейс даёт такое понимание, потому что описывает бизнес логику работы класса безотноситель того, из каких элементов состоит класс. В то время как базовый класс будет включен в состав вашего класса и будет связан понятием, что такое есть ваш класс.
  • Интерфейсы являются независимыми (от элементов входящих в класс) и могут быть использованы во многих ситуациях. Они могут быть добавлены в класс, не беспокоясь о его структурных особенностях.
  • Интерфейсы очень слабо связаны с дизайном (со структурой) класса.
  • От интерфейсов не наследуется излишняя функциональность в отличии от наследования от базового класса.
В пользу базовых классов
  • Возможность добавления разумной функциональности в порождённом классе, для работы с данными и методами базового класса.
  • Реализация одного и того же интерфейса, в нескольких классах может привести к большому количеству повторений кода , в то время как, используя базовый класс, можно сгруппировать общий код в одной реализации.
  • Абстрактный базовый класс может обеспечить реализацию по умолчанию.
  • Абстрактные базовые классы, как правило, жестко структурируют код. Это может быть желательно в некоторых случаях.
Вы можете поймать себя на том, что пытаетесь включить слишком много функций в абстрактные базовые классы , другая же крайность в разнесении функциональности на несколько базовых классов. По мере приобретения опыта, вам будет лучше видно, что имеет смысл использовать в различных ситуациях. Возможно вы будете комбинировать использование интерфейсов и базовых классов.
Рейтинг@Mail.ru Photo & Фото