уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

До сих пор я описывал основные моменты работы в C# — знакомство с Visual Studio, арифметические и логические операторы, работы с массивами и при этом не затрагивал вопросов практического применения тех или иных знаний по языку. На мой взгляд, изучение языка программирования без практики — это всё равно, что учиться водить автомобиль исключительно по книгам, то есть занятие бесполезное. Вот поэтому я и решил немного потренироваться и написать свой первый класс в C# для дальнейшего его практического применения.

В качестве объекта изучения я выбрал класс TGeodesy из Delphi, который я писал для работы с KML в Delphi. Этот класс не содержит чего-то продвинутого в плане программирования типа дженериков, работы с JSON и XML  и так далее — сугубо расчёты  с использованием арифметических операторов и немного функций из Math. Чем не объект для изучения работы с классами в C# для начинающего?

Объявление класса в C#

Определение класса в C# в общем случае можно представить следующим образом:

//[access modifier] - [class] - [identifier]
public class Customer
{
   // Fields, properties, methods and events go here...
}

Здесь представлен публичных класс с идентификатором (именем) Customer. Перед ключевым словом class указывается уровень доступа. В наем случае используется открытый класс (public), что означает, что любой пользователь может создавать его экземпляры. После ключевого слова class указывается идентификатор класса. Оставшаяся часть определения (в фигурных скобках) — это тело класса, в котором задаются данные и поведение. Поля, свойства, методы и события в классе собирательно называются членами класса.

Раз уж речь зашла об уровнях доступа, то…

Модификаторы доступа в C#

Все типы и члены типов имеют уровень доступности. Уровень доступности определяет возможность их использования из другого кода в вашей или в других сборках.

В C# возможно использование следующих модификаторов доступа:

  1. public: доступ к типу или члену возможен из любого другого кода в той же сборке или другой сборке, ссылающейся на него.
  2. private: доступ к типу или члену возможен только из кода в том же объекте class или struct.
  3. protected: доступ к типу или члену возможен только из кода в том же объекте class либо в class, производном от этого class.
  4. internal: Доступ к типу или члену возможен из любого кода в той же сборке, но не из другой сборки.
  5. protected internal: доступ к типу или члену возможен из любого кода в той сборке, где он был объявлен, или из производного class в другой сборке.
  6. private protected: доступ к типу или члену возможен только из его объявляющей сборки из кода в том же class либо в типе, производном от этого class.

Что будет делать наш первый класс C#?

Разрабатываемый класс будет выполнять преобразование декартовых координат (X,Y) в географические в системе WGS-84 (широта, долгота) и обратно. При разработке я постараюсь пока использовать только известные мне типы данных. Не исключено, что по прошествии некоторого времени я буду возвращаться к коды и вносить в него коррективы, исходя из полученных новых знаний по работе в C#.

Итак, пошаговый алгоритм разработки класса.

Создаем «скелет» будущего класса

Чтобы создать заготовку для будущего класса в Visual Studio можно выполнить следующие действия:

1) В окне «Обозреватель решений» выбрать проект в котором мы будем создавать наш класс. У меня — это проект «HelloWorld»:

2) Кликаем правой кнопкой мыши на названии проекта и в меню выбираем пункт «Добавить — Класс»:

3) Откроется новое окно «Добавление нового элемента» в котором необходимо выбрать интересующий нас элемент (Класс), а также задать название файла в котором будет этот класс храниться:

Я назвал файл «Wgs84.cs»

В результате этого Visual Studio создаст пустую заготовку для класса с названием Wgs84, которая будет выглядеть следующим образом:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace HelloWorld
{
    class Wgs84
    {
    }
}

Все определения using можно убрать (они нам сегодня не понадобятся) и оставить только одно:

using System;

Далее, после using идёт определение пространства имен (namespace). Название пространства имён должно отражать суть того, что в этом пространстве имён располагается. А так как я планирую также добавлять в свой проект и другие классы и методы для работы с географическими системами координат, то я решил назвать пространство имен Geodesy. В результате у меня получилась вот такая структура файла с будущим классом Wgs84:

using System;
namespace Geodesy
{
    public class Wgs84
    {
    }
}

Задаем константы и поля класса

В C# константы можно охарактеризовать следующими признаками:

  • Константа должна быть инициализирована при определении;
  • После определения значение константы не может быть изменено.

Задается константа также, как и в Delphi с использованием ключевого слова const, например:

const double Radius = 6378137

При этом следует помнить, что пространство имен не может напрямую содержать константы, поля или методы. Что это значит? Например, в Delphi мы могли спокойно сделать вот так:

unit Geodesy;
 
interface
 
uses System.Math, System.Classes, System.SysUtils;
 
const
  R = 6378137; // Экваториальный радиус Земли по WGS-84

И потом использовать константу в любых своих классах, методах и так далее. В C# же вот такая запись:

using System;
namespace Geodesy
{
    public const double EartRadius = 6378137;
}

Приведет сразу к ошибке: «Ошибка CS0116 Пространство имен не может напрямую включать в себя такие члены, как поля или методы.» 

То есть, в нашем случае, мы должны константу «завернуть» в наш класс и написать код следующим образом:

using System; 
namespace Geodesy //пространство имен
{
    public class Wgs84 //класс
    {
        /* Константы для системы WGS-84 */
        protected const double Radius = 6378137; 
    }
}

Соответственно, так как я не планирую давать доступ к этой константе извне другим классам и методам, то константа была помечена модификатором доступа protected. Всего же, для дальнейшей работы нам потребуется три константы:

public class Wgs84
    {
        /* Константы для системы WGS-84 */
        protected const double Radius = 6378137; //экваториальный радиус Земли в WGS-84
        protected const double recF = 298.257223563; //сжатие элипсоида в WGS-84  
        protected const double f = 1.0 / recF;
    }

Что касается полей класса, то они определяются аналогично, например:

protected double Bo;

При этом, если в Delphi правилом хорошего тона считается начинать название поля класса с буквы «F», например, «FName«, «FOrder» и так далее, то в C#, на сколько я понимаю, такого делать не требуется и используется следующее правило: для именования полей, доступных вне класса (например, public), используется стиль pascal case (первая буква каждого слова пишется с большой буквы), для private полей — camel case (первая буква первого слова — с маленькой буквы, остальные — с большой).

Для работы нашего класса я определил следующие поля:

/* Поля класса, используемые для хранения вспомогательных значений */
protected double fe = Math.Sqrt(f * (2 - f));
protected double bo;
protected double lo;
protected double w;
/* Поля, используемые в свойствах класса */
private double baseLatitude; //широта точки привязки
private double baseLongitude;//долгота точки привязки

Свойства класса

Для начала немного справки Microsoft:

  • Свойства позволяют классу предоставлять общий способ получения и задания значений, скрывая при этом код реализации или проверки.
  • Метод доступа get используется для возврата значения свойства, а метод доступа set — для присвоения нового значения. Эти методы также могут иметь различные уровни доступа.
  • Ключевое слово value используется для определения значения, присваиваемого методом доступа set.
  • Свойства могут быть доступны для чтения и записи (они имеют оба метода доступа — get и set), только для чтения (они имеют метод доступа get, но не имеют метода доступа set) или только для записи (они имеют метод доступа set, но не имеют метода доступа get).
  • Простые свойства, не требующие пользовательского кода метода доступа, можно реализовать как определения текста выражений или как автоматически реализуемые свойства.

Теперь попробуем разобраться с тем, что написано выше. Итак, для свойства можно определить два метода — get и set, что, по-сути, является аналогом read и write в Delphi. В самом общем случае, свойство класса в C# можно описать следующим образом:

private double prop;
public double Prop 
 { get { return prop; }
   set { prop = value; }
 }

Вначале определено поле с именем prop, которое помечено модификатором доступа private. Далее определено публично (public) свойство с идентификатором Prop (с большой буквы). У это свойства определены два метода — get и set, то есть наше свойство можно и записывать и читать. Метод get у нас возвращает (return) значение поля prop, а метод set — присваивает полю prop передаваемое значение (value). Переводя этот код на язык Delphi мы бы получили вот такой код:

  private
    Fprop: double;
    procedure SetProp(const Value: double);
    function GetProp: double;
  public
    property Prop: double read GetProp write SetProp;
  end;
implementation
 
{ TMyCalss }
 
function TMyCalss.GetProp: double;
begin
  Result:=Fprop
end;
 
procedure TMyCalss.SetProp(const Value: double);
begin
  FProp := Value;
end;

Знающий человек сказал бы, что этот код Delphi можно сократить вот до такого без каких либо потерь:

  private
    Fprop: double;
  public
    property Prop: double read Fprop write Fprop;
  end;

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

public double TotalPurchases { get; set; }

Как видите, таким свойствам, во-первых, не требуется описание поля, а, во-вторых, get/set задаются без какого-либо кода. При объявлении свойства, как показано выше, компилятор создает закрытое анонимное резервное поле, которое может быть доступно только через методы доступа get и set свойства.

Так что, для описания свойств класса мы можем с вами руководствоваться следующими правилами:

  1. Если для свойства необходимо описание какой-либо дополнительной логики, например, значение заданное пользователем должно каким-либо образом пересчитываться и храниться, то используем полное описание с get/set
  2. Если дополнительная логика не требуется, то создаем автоматически реализуемые свойства.

Для своего класса я определил два автоматически реализуемых свойства:

/*Координаты базовой точки в декартовой системе координат*/
public double X0 { get; set; }
public double Y0 { get; set; }

И два свойства с так называемыми резервными полями:

/*Координаты базовой точки в системе Wgs-84*/
        public double BaseLatitude
        { get { return baseLatitude; }
            set {
                baseLatitude = value;
                bo = DegToRad(baseLatitude);
                w = Math.Sqrt(1 - Math.Pow(fe * Math.Sin(bo), 2));
            }
        }
        public double BaseLongitude { get { return baseLongitude; }
            set
            { baseLongitude = value;
                lo = DegToRad(baseLongitude);
            }
        }

При задании этих свойств пользователем происходит преобразование координат из градусов в радианы, а также рассчитывается вспомогательное поле w, которое будет нам необходимо в методах класса. Так как я пока не знаю, потребуются ли мне значения географических координат в форме градусов, а не радиан, то пока решил сохранять введенное пользователем значение в поле класса. Возможно, что в дальнейшем код методов get set у свойств изменится и в классе будет на два поля меньше.

Методы класса

Методы в C# описываются следующим образом:

protected double MethodName(double arg)

то есть, в начале идет модификатор доступа, затем — тип возвращаемого значения, затем — имя метода и в круглых скобках аргументы (передаваемые и возвращаемые данные).

Был немного удивлен, когда не обнаружил в классе Math (аналог модуля Math в Delphi) методов перевода градусов в радианы и наоборот (DegToRad / RadToDeg). Поэтому пришлось их быстренько написать самому. Согласен, что по-хорошему, эти методы прямого отношения к системе Wgs-84 не имеют и их бы следовало вынести в отдельный класс, но пока, ради экономии времени, решал их оставить в своем классе. Выглядят эти методы следующим образом:
/*Вспомогательные методы*/

protected double DegToRad(double deg)
  {
     return deg * (Math.PI / 180.0);
  }
protected double RadToDeg(double rad)
 {
    return rad * (180.0 / Math.PI);
 }

Теперь осталось дописать ещё два метода — для перевода координат из одной системы в другую:

/*Перевод декартовых координат в географические
         double X, Y - координаты точки, которые необходимо перевести в широту и долготу
         ref double B - широта
         ref double L - долгота
         */
        public void XY2BL(
            double X, double Y, ref double B, ref double L)
        {
            double dB = (Y - Y0) /(radius * (1 - Math.Pow(fe, 2)) / Math.Pow(w, 3));
            double dL = (X - X0) / (radius * Math.Cos(bo) / w);
            B = baseLatitude + RadToDeg(dB);
            L = baseLongitude + RadToDeg(dL);
        }
 
        /* Перевод географических координат в декартовы 
         double B, L - широта и долгота точки
         ref double X, ref double Y - координаты точки в декартовой системе
         */
        public void BL2XY(double B, double L, ref double X, ref double Y)
        {
            double dY = (DegToRad(B) - bo) * (radius * (1 - Math.Pow(fe, 2)) / Math.Pow(w, 3));
            double dX = (DegToRad(L) - lo) * ((radius * Math.Cos(bo)) / w);
            X = X0 + dX;
            Y = Y0 + dY;
        }

Теперь наш класс может получать от пользователя координаты базовых точек и переводить любые координаты из одной системы в другую. Полный код класса представлен ниже.

Код класса Wgs84

using System;
namespace Geodesy
{
    // Сводка:
    //     Предоставляет методы для преобразования географических координат в декартовы и наоборот
    public class Wgs84
    {
        /* Константы для системы WGS-84 */
        protected const double radius = 6378137; //экваториальный радиус Земли в WGS-84
        protected const double recF = 298.257223563; //сжатие элипсоида в WGS-84  
        protected const double f = 1.0 / recF;
        /* Поля класса для хранение вспомогательных значений */
        protected double fe = Math.Sqrt(f * (2 - f));
        protected double bo;
        protected double lo;
        protected double w;
        /*Вспомогательные методы*/
        protected double DegToRad(double deg)
        {
            return deg * (Math.PI / 180.0);
        }
        protected double RadToDeg(double rad)
        {
            return rad * (180.0 / Math.PI);
        }
 
        /* Поля, используемые в свойствах  */
        private double baseLatitude; //широта точки привязки
        private double baseLongitude;//долгота точки привязки
        /* Свойства */
        public double BaseLatitude
        { get { return baseLatitude; }
            set {
                baseLatitude = value;
                bo = DegToRad(baseLatitude);
                w = Math.Sqrt(1 - Math.Pow(fe * Math.Sin(bo), 2));
            }
        }
        public double BaseLongitude { get { return baseLongitude; }
            set
            { baseLongitude = value;
                lo = DegToRad(baseLongitude);
            }
        }
        public double X0 { get; set; }
        public double Y0 { get; set; }
 
        /* Методы */
 
        /*Перевод декартовых координат в географические
         double X, Y - координаты точки, которые необходимо перевести в широту и долготу
         ref double B - широта
         ref double L - долгота
         */
        public void XY2BL(
            double X, double Y, ref double B, ref double L)
        {
            double dB = (Y - Y0) /(radius * (1 - Math.Pow(fe, 2)) / Math.Pow(w, 3));
            double dL = (X - X0) / (radius * Math.Cos(bo) / w);
            B = baseLatitude + RadToDeg(dB);
            L = baseLongitude + RadToDeg(dL);
        }
 
        /* Перевод географических координат в декартовы 
         double B, L - широта и долгота точки
         ref double X, ref double Y - координаты точки в декартовой системе
         */
        public void BL2XY(double B, double L, ref double X, ref double Y)
        {
            double dY = (DegToRad(B) - bo) * (radius * (1 - Math.Pow(fe, 2)) / Math.Pow(w, 3));
            double dX = (DegToRad(L) - lo) * ((radius * Math.Cos(bo)) / w);
            X = X0 + dX;
            Y = Y0 + dY;
        }
    };
}

Использование класса в приложении

Попробуем использовать разработанный класс в приложении C#. Для этого мы должны включить пространство имен Geodesy в наше приложение, используя директиву using.

using System;
using Geodesy; //используем Geodesy в нашем приложении
 
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
 
        } 
    }
}

Теперь мы можем создать объект, реализующий наш класс Wgs84 и задать ему необходимые свойства. Сделать это можно, опять же несколькими способами.
Первый способ:

//создаем объект
Wgs84 ConvertCoord = new Wgs84();
//задаем свойства
ConvertCoord.BaseLatitude = 55.55;
ConvertCoord.BaseLongitude = 74.75;
ConvertCoord.X0 = 0;
ConvertCoord.Y0 = 0;

А можем воспользоваться более лаконичной формой записи:

Wgs84 Coord = new Wgs84
{
  BaseLatitude = 55.55,
  BaseLongitude = 74.75,
  X0 = 0,
  Y0 = 0
};

Теперь, чтобы воспользоваться переводом координат из одной системы в другую мы можем написать вот так:

double B = 0;
double L = 0;
Coord.XY2BL(100, 100, ref B, ref L);
Console.WriteLine("Широта {0}; Долгота {1} ", B, L);

Полный код программы, показывающей работу с классом представлен ниже:

using System;
using Geodesy;
 
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Wgs84 Coord = new Wgs84
            {
                BaseLatitude = 55.55,
                BaseLongitude = 74.75,
                X0 = 0,
                Y0 = 0
            };
            double B = 0;
            double L = 0;
            Coord.XY2BL(100, 100, ref B, ref L);
            Console.WriteLine("Широта {0}; Долгота {1} ", B, L);
            Console.ReadLine();
        } 
    }
}

Подведем итог

Сегодня мы создали первый наш класс в C#. Да, возможно он не идеален и профессиональный разработчик в C# найдет в нем массу мест для улучшения, но, тем не менее общее представление о работе с классами в C# должно сложиться.

Конечно, эта статья не охватывает абсолютно все аспекты работы с классами в C#. Например, я не рассмотрел работу с событиями и другие моменты,которые в дальнейшем нам пригодятся в работе, однако,при этом стоит помнить, что всё в .NET так или иначе завязано на понятиях «класс» и «объект» поэтому к работе с классами я ещё буду возвращаться ни один раз.

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии