. Net очима дельфійців – перші враження., ASP, Програмування, статті

Частина 1. C #

Зміст

Введення

Чого немає в C #

Чого немає в Delphi

Висновок

Введення

При знайомстві з новою мовою програмування будь-якого програміста в першу чергу цікавить семантична основа мови, тобто наскільки його виразні можливості дозволяють реалізувати звичні логічні конструкції.

Враховуючи те, що C #, як і Delphi, виступає одночасно в двох якостях, тобто з одного боку, є семантично строго певною мовою програмування і, з іншого боку, використовує поставляються в складі. Net бібліотеки класів і компонентів, на першому етапі має сенс сконцентруватися на самому мові програмування, тому що вивчення та порівняльний аналіз бібліотек класів – набагато більш об’ємна робота.

Чого немає в C #

Відсутність в C # деяких речей обумовлено тим, що C # є <чисто> об’єктним мовою програмування, а Delphi – Гібридним. Тим не менш, в C # або є, або можуть бути легко реалізовані самостійно практично всі семантично еквівалентні конструкції.

Отже, C # не надає наступні можливості (їх розгляд не увійшло в справжній документ в силу або другорядного значення, чи наявності семантично еквівалентних реалізацій в бібліотеці CLR):

Більш суттєві конструкції, яких немає в
C#:

Процедури, функції

Якщо вважати, що процедури – це просто функції, які не повертають жодного значення, то семантична навантаження процедур і функцій в Delphi однакова. Це – виконання деякого фрагмента коду, який, можливо, залежить від вхідних параметрів:

	procedure A(aParam: integer);
	begin
	  // ...
	end;
	function B(aParam: integer): integer;
	begin
	  // ...
	  Result := 0;
	end;
	A(1);
	X := B(1);

В C # семантичним еквівалентом процедур і функцій виступають статичні методи класів.

/ / Клас-обгортка
	class Func { / / Статичний метод без значення, що повертається - еквівалент процедури
	  static public void A(int aParam); / / Статичний метод - еквівалент функції
	  static public int B(int aParam);
	} / / Виклик процедури
	Func.A(1); / / Виклик функції
	int X := Func.B(1);

Глобальні константи

Семантична навантаження в Delphi – визначення значень примітивних типів даних, доступних з будь-якого місця коду та незмінних в процесі виконання програми.

	const A = 100; const B = "рядок";
	D := A;
	ShowMessage(B);

Семантичний еквівалент в C # – статичні константи.

/ / Клас-обгортка
	class Const { / / Опис констант
	  public const int A = 100; public const string B = "рядок";
	} / / Використання констант
	int a = Const.A;
	MessageBox.Show(Const.B);

Крім статичних констант C # надає механізм статичних полів <тільки для читання », який дозволяє програмісту використовувати як констант не лише примітивні значення, а й об'єкти. Приклад коду:

/ / Клас-обгортка
	class Const { / / Число-константа
	  public static readonly int A = 1; / / Об'єкт-константа
	  public static readonly MyObject Obj = new MyObject();
	}

Глобальні змінні

Семантична навантаження в Delphi – формування об’єктів програми (як примітивних типів, так і складних), доступних з будь-якого місця коду і, можливо, змінюваних в процесі виконання програми.

	var A: integer;
	B := A;
	A := 1;

Семантичний еквівалент в C # – статичні поля класів.

/ / Клас-обгортка
	class Globals { / / Визначення статичних змінних / / Ініціалізація за замовчуванням = 0
	  public static int A; / / Одночасні опис і ініціалізація
	  public static int B = 1;
	} / / Використання статичних змінних
	int a = Globals.A;
	Globals.A = 1;
	int b = Globals.B;
	Globals.B = 1;

Попереднє оголошення типів

Попереднє оголошення типів на самому справі не передбачено загальною теорією об’єктно-орієнтованого програмування і є приватним рішенням Delphi, спрямованим на ослаблення правила, яке було введено ще в класичному Pascal, – <Всі типи даних, що використовуються для побудови складних типів, повинні бути або примітивного типу, або описані до їх використання>.

Приклад коду на Delphi:

	type
	  TMyObject1 = class;
	  TMyObject2 = class;  
	  TMyObject1 = class
	    function GetChild(Index: int): TMyObject2;
	  end;
	  TMyObject2 = class
	    property Owner: TMyObject1 read fOwner;
	  end;

В C # предопісаніе типів не потрібно, тому що в межах області видимості класів (обрамляє клас, простір імен) порядок оголошення неістотний. Таке рішення спрощує написання коду:

	namespace MyObjects {
	  public class TMyObject1 {
	    public TMyObject2 GetChild(int Index) { ... }
	  }
	  public class TMyObject2 {
	    public TMyObject1 Owner {
	      get { return owner; }
	    }
	  }
	}

Типізовані константи

Типізовані константи в Delphi дозволяють зберігати не тільки значення примітивних типів, а й масиви, записи, а також покажчики, включаючи покажчики на процедури і функції:

	const A: integer = 1;
	const B: array [1..3] of integer = (1, 2, 3);

В C # константи завжди типізовані – як при використанні модифікатора const, так і readonly.

У Delphi при використанні директиви компілятора {$ J +} (встановлено за умовчанням) тіпізованние константи ведуть себе як звичайні змінні, які ініціалізувалися одночасно з описом, тобто їх значення може бути змінено в ході виконання програми:

	const A: integer = 1;
	X := A;
	A := 2;

В C # діють більш суворі правила – якщо константа, то поміняти її значення неможливо. Якщо ж використовується поле <Тільки для читання ", то його вміст може бути змінено в контексті об'єкту:

/ / Клас-обгортка
	public class Const { / / Число-константа
	  public readonly int A = 1; / / Метод змінює значення поля A
	  public void ChangeA(int NewValue) { A = NewValue; }
	}

При використанні тіпізованних констант в Як ініціалізіруемих змінних в області видимості підпрограми (Методу, процедури, функції) в Delphi спостерігається недокументовані побічний ефект – дані, записані в локальні тіпізованние константи, зберігаються між викликами підпрограм:

	procedure A;
	const B: integer = 0;
	begin
	  Inc(B);
	  ShowMessage(IntToStr(B));
	end;
	procedure Do;
	begin
	  A;
	  A;
	  A;
	end;

Результати висновки: 1, 2, 3

В C # подібний побічний ефект відсутній. Взагалі, стандартизація C # в якості міжнародного стандарту (ECMA, 2001 рік) гарантує відсутність побічних ефектів. Тому якщо в програмі C # необхідно зберігати деякий стан між викликами підпрограм (методів), використовуються стандартні засоби, зокрема, статичні або звичайні поля класів.

Const-параметри

В Delphi семантичний зміст const-параметрів полягає у вказівці компілятору на можливість оптимізації передачі в функцію (процедуру, метод) незмінної посилання на деякий об’єкт програми. Так, наприклад, конструкція типу:

	procedure A(const S: string);

означає, що в якості параметра процедури передаватиметься посилання на рядок (при цьому копіювання вмісту рядка в стек виклику процедури не відбувається). Крім того, вміст рядка S всередині процедури змінити не можна.

В C # не передбачено прямого еквівалента const-параметрів. Тим не менш, у разі необхідності може бути побудована семантично еквівалентна конструкція (аналогія вищенаведеному прикладу):

	class ReadOnlyString {
	  ReadOnlyString(string S) { this.S = S; }
	  public readonly string S;
	  static void Test(ReadOnlyString s) { Console.Write(s.S); }
	  static void Main() { string s = "перевірка const-параметрів";
	    ReadOnlyString.Test(new ReadOnlyString(s));
	  }
	}

Наведений код ілюструє використання класів-<обгорток> (т.зв. wrappers) і полів <тільки для читання ».

Покажчики

В Delphi покажчики найчастіше використовуються для <тонкого> управління такими конструкціями, як записи. Зокрема, при передачі записи в якості параметра в підпрограму (процедуру, функцію або метод) відбувається побайтно копіювання в стек виклику, що може призводити до серйозних накладних витрат. Альтернативне рішення – передати в якості параметра покажчик на запис і вже через нього усередині підпрограми отримати доступ до елементів запису.

В C # наведений приклад використання покажчиків на записи більш витончено і безпечно реалізується за використанням такої конструкції, як struct. Структури, як і записи Delphi, можуть використовуватися для зберігання даних і, що більш важливо з точки зору семантики, є об’єктами, що передаються не по посиланню, а за значенням.

Насправді, в C # є всього лише одна можливість використовувати покажчики – т.зв. <Небезпечний> код (unsafe code). Особливості його використання визначено в стандарті C # досить докладно (специфікація C #, додаток A). Однак, необхідно зазначити, що в практичному програмуванні рідко доводиться використовувати покажчики для інших цілей, крім оптимізації продуктивності. Тому, враховуючи класичне правило <80/20> (<80% пива випивають двадцятьма відсотками населення>, або <80% використовуваних ресурсів програми доводиться на 20% коду>, або <80% обсягу роботи дозволяє поліпшити продуктивність тільки на 20%>), можна акцентуватися на оптимізації коду (в термінах C # – використання небезпечного коду) тільки при необхідності і тільки тоді, коли виявлені ті самі 20% коду, які використовують 80% ресурсів.

Чого немає в Delphi

Тепер можна розглянути ті переваги, які має C # в порівнянні з Delphi (порядок перерахування довільний і ні в якій мірі не відображає об’єктивні пріоритети або суб’єктивні переваги):

Опис змінних в коді програми

Можливість опису змінних в коді програми прийшла в C # з C + +. Приклад коду:

	class VarTest {
	  static void Main() {
	    int a;
	    int b, c = 1, d;
	    for (int i = 0; i < 10; i++) {
	      int x = i * 2;
	    }
	  }
	}

У цьому прикладі продемонстровані відразу кілька можливостей, відсутніх в Delphi:

Можливість передачі в метод змінного кількості параметрів

Приклад коду, що ілюструє можливість передачі в метод змінного кількості параметрів:

	class Test {
	  static void F(params int[] args) { / / Обробка параметрів
	  }
	  static void Main() {
	    F();
	    F(1);
	    F(1, 2);
	    F(1, 2, 3);
	    F(new int[] {1, 2, 3, 4});
	  }
	}

В Delphi відсутність змінної кількості параметрів можна частково компенсувати або використанням значень параметрів за замовчуванням, або передачею в якості параметра відкритого масиву.

Проте в першому випадку все одно кількість параметрів обмежена і має бути визначено при написанні відповідної підпрограми.

У другому ж випадку, хоча і спостерігається схожість з C #, є істотні обмеження:

Автоматичне видалення об’єктів

При програмуванні в термінах об’єктів Delphi доводиться постійно враховувати, коли створюються об’єкти певного класу і, що ще важливіше, хто і коли їх видаляє.

Ситуація дещо спрощується при побудові візуальних програм та використанні компонентів – встановивши компонент на форму (або модуль даних), тим самим ми визначаємо механізм створення і видалення об’єктів відповідного класу, коли форма-власник створює об’єкт в процесі своєї ініціалізації і видаляє його в момент свого видалення.

При побудові невізуальних додатків (Наприклад, реалізація бізнес-правил на проміжному шарі багаторівневого програми) можна використовувати механізм автоматичного видалення COM-об’єктів або <інтерфейсних> об’єктів (об’єктів, що є спадкоємцями від TInterfacedObject).

Втім, необхідно визнати, що автоматичне видалення об’єктів в Delphi реалізовано не кращим чином, тому що:

Більш того, <стандартні> об’єкти Delphi, які часто використовуються при побудові прикладних об’єктів, наприклад, списки, стеки, бітові масиви і пр., не підтримують механізм автоматичного видалення, тому на практиці доводиться або використовувати змішану модель, в якій лише деякі об’єкти видаляються автоматично, або з метою стандартизації коду взагалі відмовлятися від автоматичного видалення.

Розглянемо просту семантичну конструкцію – Завантаження списку об’єктів.

В Delphi типова реалізація виглядають приблизно так:

	procedure LoadList(aList: TObjectList);
	begin
	  aList.Clear; / / Заповнення списку
	end;
	. . .
	try
	  MyObjectList = TObjectList.Create;
	  LoadList(MyObjectList); / / Далі - використання об'єктів зі списку
	finally
	  MyObjectList.Free;
	end;

Насправді через обмеження Delphi (TObjectList не може віддалятися автоматично) семантика наведеного коду розбивається на дві окремі фази:

В C # аналогічну дію (завантаження списку об’єктів) реалізується простіше і семантично точніше:

	class ListLoadTest {
	  Collection LoadList() {
	    Collection c = new Collection(); / / Безпосередня завантаження об'єктів і додавання їх в колекцію
	    return c;
	  }
	  static void Main() {
	    Collection c = LoadList();
	    foreach (MyObject in c) { / / Щось зробити з черговим об'єктом зі списку
	    }
	  }
	}

Строго кажучи, в наведеному коді C # навіть два переваги:

Таким чином, досить проста річ – автоматичне видалення об’єктів, – дозволяє писати більш точний (Семантично) і зрозумілий код.

Поля класів <тільки для читання>

В Delphi для того, щоб реалізувати концепцію <поле тільки для читання », можна використовувати властивості (Properties), при цьому доводиться писати щось подібне:

	type
	  TMyObject = class
	  private
	    fData: integer
	  public / / Еквівалент поля для читання
	    property Data: integer read fData;
	  end;

Поля <тільки для читання> в C # введені на рівні мови:

	class A {
	  public readonly int Data;
	}

Різниця між (статичними) константами і полями <тільки для читання> полягає в тому, що якщо константи можуть бути обчислені на стадії компіляції, що справедливо, наприклад, для простих типів, то значення полів <тільки для читання> визначаються тільки на стадії виконання програми.

Це призводить до цікавих наслідків. В стандарті C # розглядається ситуація, коли є бібліотека і використовує її програма, компільовані роздільно. Якщо в бібліотеці використовувати константу, то при зміні її значення (і перекомпіляції бібліотеки) потрібно перекомпілювати і програму. Якщо ж використовувати поле <тільки для читання ", то програму перекомпілювати не обов'язково, тому що значення поля визначається на стадії виконання.

Індексатори

У Delphi можна реалізувати властивість класу типу масив і, встановивши для нього атрибут default, отримати деякий подобу індексатора:

	type
	  TMyObject = class
	  public
	    property Items[Index: integer]: string read GetItem; default;
	  end;

Тоді в коді можна використовувати дві еквівалентні конструкції:

	S := MyObject.Items[I]; 
	S := MyObject[I]; 

Другий рядок якраз і демонструє основну ідею індексаторів C # – можливість звертатися до об’єкта як до масиву. Проте в Delphi є істотне обмеження – можна використовувати тільки одну властивість (типу масиву) за замовчуванням.

В C # можна реалізувати довільне кількість індексаторів для класу:

	class A {
	  int this[int Index] { . . . }
	  string this[char Col, int Row] { . . . }
	  static void Main() {
	    A a = new A();
	    for (int i = 0; i < a.Count; i++) 
	      Console.Writeln(a[i].ToString());
	    for (char c = "a"; c < "z"; c++)
	      for (int r = 1; r < 100; r++)
	        Console.Writeln(a[c, r]);
	  }
	}

Делегати

В Delphi обробники подій грають роль делегатів – <делегують> реальну роботу зовнішнього об’єкту. Однак через того, що Delphi є гібридним мовою програмування, зустрічаються ситуації, коли семантично еквівалентні завдання реалізуються різними способами. Так, наприклад, в класу TList при сортуванні використовується покажчик на функцію порівняння елементів:

	type TListSortCompare = function (Item1, Item2: Pointer): Integer;
	procedure Sort(Compare: TListSortCompare);

Тобто насправді завдання порівняння елементів списку також <делегується> функції, зовнішньої по відношенню до об’єкта
TList.

Така семантична неоднозначність аж ніяк не спрощує програмування.

В C # реалізований більш загальний і однозначний підхід – делегати:

	delegate void SimpleDelegate();
	class Test {
	  static void F() {
	    System.Console.WriteLine("Test.F");
	  }
	  static void Main() {
	    SimpleDelegate d = new SimpleDelegate(F);
	    d();
	  }
	}

Причому як реалізації делегата може виступати як статичний, так і звичайний метод.

Можливість реалізації моделі обробки подій <один-багато>

Хоча за замовчуванням в C # компоненти реалізують схему підключення обробників подій <один-до-одного », при необхідності може бути реалізована модель <один-багато>. Основа такої можливості полягає в тому, що в якості обробників подій використовуються делегати, а їх підключення до компонента реалізується за допомогою перевантажується методу:

	class Control: Component {
	  // Unique keys for events
	  static readonly object mouseDownEventKey = new object();
	  static readonly object mouseUpEventKey = new object();
	  // Return event handler associated with key
	  protected delegate GetEventHandler(object key) {...}
	  // Add event handler associated with key
	  protected void AddEventHandler(object key, Delegate handler) {...}
	  // Remove event handler associated with key
	  protected void RemoveEventHandler(object key, Delegate handler) {...}
	  // MouseDown event
	  public event MouseEventHandler MouseDown {
	    add { AddEventHandler(mouseDownEventKey, value); }
	    remove { RemoveEventHandler(mouseDownEventKey, value); }
	  }
	  // MouseUp event
	  public event MouseEventHandler MouseUp {
	    add { AddEventHandler(mouseUpEventKey, value); }
	    remove { RemoveEventHandler(mouseUpEventKey, value); }
	  }
	}

Наведений код узятий із специфікації C # і, хоча виглядає відносно об’ємним, прозоро ілюструє можливість використання довільного внутрішнього механізму для зберігання посилань на обробники і підключення довільної кількості зовнішніх обробників.

Цикл foreach

Цикл foreach перейнятий в C # з Visual Basic. Вийшла досить зручна річ:

	class Test {
	  static void Main() {
	    double[] values = {1.2, 2.3, 3.4, 4.5};
	    foreach (double elementValue in values)
	      Console.Write("{0} ", elementValue);
	  }
	}

Семантично аналогічний код в Delphi виглядає більш громіздким через необхідність використовувати ітератор (Змінна I), а також (в загальному випадку) обчислювати кордону масиву:

	procedure A;
	const Values: array [1..4] of double = (1.2, 2.3, 3.4, 4.5);
	var I: integer;
	begin
	  for I := Low(Values) to High(Values) do
	    ShowMessage(FloatToStr(Values[I]));
	end;

Статичні конструктори

Деякий семантичний аналог статичним конструкторам в Delphi – секція initialize.

На жаль, в Delphi порядок виклику секцій initialize відповідає порядку підключення модулів. Така практика може приводити до несподіваних помилок – спочатку розраховуючи на конкретний порядок підключення модулів, можна випадково в процесі розробки змінити цей порядок і в результаті, наприклад, отримати звернення до неіснуючого або некоректно ініціалізованої глобальному об’єкту програми.

C # надає більш суворе об’єктне рішення, яке, зокрема, дозволяє управляти правами доступу:

	class A {
	  static protected A GlobalA;
	  static A() { GlobalA = new A; }
	}

В C # порядок роботи статичного конструктора визначений тільки на рівні класу, при наявності ж декількох класів з статичними конструкторами порядок їх активізації не фіксований. Такий підхід змушує більш ретельно проектувати програму з самого початку і виключає появу в подальшому помилок, аналогічних описаної вище.

Оператори класів

Оператори класів в C # майже еквівалентні операторам класів в C + +:

	public class Digit {
	  byte value;
	  public Digit(byte value) {
	    if (value < 0 || value > 9) throw new ArgumentException();
	    this.value = value;
	  }
	  public static Digit operator+(Digit a, Digit b) {
	    return new Digit(a.value + b.value);
	  }
	  static public Main() {
	    Digit a = new Digit(5);
	    Digit b = new Digit(3);
	    Digit plus = a + b;
	  }
	}

У порівнянні з C + + в C # строго і однозначно визначено порядок реалізації користувальницьких правил перетворення об’єктів (перетворення розглядаються як окремий випадок операторів).

Примітка: Delphi не має механізмів, еквівалентних операторам класів.

Структури

Структури в C # аналогічні записам в Delphi в тому сенсі, що є даними, переданими за значенням, а не по посиланням.

Насправді семантика структур в C # ближче до класам, за винятком двох основних обмежень:

Приклад структури:

	struct Point {
	  public int x, y;
	  public Point(int x, int y) {
	    this.x = x;
	    this.y = y;
	  }
	}

Використання структур може як підвищити продуктивність програми (наприклад, при розміщенні великої кількості дрібних об’єктів краще використовувати структури), так і погіршити її (якщо використовується структура, яка містить великі обсяги даних, то при передачі її в якості параметра буде виконуватися зайве копіювання).

Існує емпіричне правило: якщо обсяг даних менше 16 байт, то для їх зберігання краще використовувати структуру, якщо більше – клас.

Атрибути

Найцікавіша можливість C #, відсутня як в Delphi, так і в інших найбільш популярних мовах програмування (VB, C + +, Java), – атрибути:

	[Help("http://www.microsoft.com/.../Class1.htm")]
	public class Class1 {
	  [Help("http://www.microsoft.com/.../Class1.htm", Topic = "F")]
	  public void F() {}
	}

Атрибути схожі на властивості класів Delphi, за винятком того, що їх значення встановлюються на стадії компіляції і в процесі виконання програми можуть бути тільки лічені. Однак сфера застосування атрибутів в поставляється бібліотеці класів CLR досить широка – від зберігання допоміжної інформації декларативного характеру до забезпечення сумісності об’єктів. Net з COM (атрибути сумісності з COM описані в додатку B специфікації C #).

Звички, сформовані під впливом Delphi, не дозволяють навіть відразу придумати, для чого можна використовувати атрибути, однак поле діяльності тут проглядається широке – від відстеження версій алгоритмів до контролю за сумісністю програм.

Можливість використовувати російську мову для імен об’єктів програми

Відповідно до специфікації C # для іменування об’єктів програми (класи, методи, змінні і пр.) використовуються символи Unicode. Звідси випливає (і реально підтверджено), що як імена можна використовувати російські назви, наприклад:

class ЕлементУчета { public int readonly ІнвентарнийНомер; public string readonly Найменування; public double РассчітатьАмортізацію (int ЛетВЕксплуатаціі) {... }
	}

Звичайно, використання російської мови в коді програми – питання спірне. Тим не менш, така можливість є. Використовувати ж її чи ні – питання стандартів написання коду в межах робочої групи (відділу, підприємства, корпорації).

Висновок

Безумовно, викладені вище моменти не можуть претендувати на абсолютну повноту і глибоку деталізацію. Тим не менш, навіть на їх основі напрошується природний висновок – C # є дуже цікавою мовою програмування, в порівнянні з Delphi в багатьох аспектах навіть більш потужним і в той же час строгим.

Чи можна на ньому писати реальні промислові завдання?

Якщо орієнтуватися на монолітні програми в стилі АРМ, то швидше за все потрібно більш детально проаналізувати можливості виконання програм. Net на конкретній апаратній платформі (Навряд чи Framework.Net зможе працювати на 486 з 8М ОЗУ). Якщо ж зробити акцент на багаторівневих додатках, то C # для реалізації проміжного шару в порівнянні з Delphi є більш перспективним кандидатом.

Загальний відповідь на поставлене питання – писати реальні завдання на C # цілком можна, і, швидше за все, навіть з меншими витратами, ніж на Delphi.

Чи важко дельфійців освоїти C #?

Можна стверджувати, що ні, тому що в основі і Delphi, і C # лежить парадигма об’єктно-орієнтованого програмування. Програміст, який може в Delphi писати код в термінах об’єктів, а не тільки на рівні обробників стандартних компонентів, зможе писати і на C #.

Втім, C # – не єдина мова, яку можна використовувати в. Net. Навіть у штатній постачання Visual Studio. Net разом з C # йде C + + і VB.Net, а взагалі список мов програмування, реалізованих для платформи. Net, вже на сьогодні перевалив за десяток. Може бути, і Borland коли-небудь випустить Delphi.Net – не дарма <Батьком> C # є якраз автор Delphi.

Чи чекати дельфійцям виходу Delphi.Net?

На це питання може відповісти тільки фірма Borland. Насправді, якщо глибше порозбирали в питанні декларованої сумісності програм, написаних на Delphi 6 і Kylix, то виявляється, що про повну сумісність не йдеться – навіть у однойменних класів VCL і CLX зустрічаються різні інтерфейси. Тому можна припустити, що Delphi.Net буде сумісна з <звичайної> Delphi теж не на всі 100%. У чому ж тоді буде перевага Delphi.Net перед тим же C #? Для <закостенілого> дельфійців – тільки в знайомому синтаксисі. Ну а поки Delphi.Net вийде у світ, можна і C # освоїти (тим більше, що його коріння лежить в C + +, а C + + завжди вважався більш потужним мовою, ніж Pascal), або можна на VB.Net писати. Але робити це можна вже СЬОГОДНІ.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*