Перейти к содержанию

Как писать программы на C#


Рекомендуемые сообщения

Написать эту тему меня подтолкнуло то, что о C# у нас мало информации. Это просто безобразие. Надо о нём обязательно поговорить особенно в контексте advanced gamehaking.

Сразу перейдем к этому контексту без нудного вступления о C#. C# (Си-шарпу) успел найти применение некоторый человек (не помню имени) над проектом создания модов к GTA 4. Я так понял, что он изучил код игры и написал главный модуль и заголовочные описания к нему на VC++ 2005. Далее он написал на C# описания того как взаимодействовать с модулем, а дальше самое интересное. Пользователи могут писать свои скрипты на .NET используя последние упомянутые описания.

Итак что сделал автор

1) Изучил код игры

2) Написал главный модуль и заголовочные описания к нему на VC++ позволяющие изменять поведения игры (писал конечно же с ассемблерными вставками)

3) Написал на C# описания работы с этим модулем.

4) Теперь пользователь может писать свои скрипты на NET языках (C#, VB.NET, F#, Delphi.NET и т.п.)

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

Разработчиками .NET очень многое уже написано и вам остаётся это использовать. Короче на C# логику удобнее писать чем на VC++. Например, очень легко на C# написать различные условия каких-то игровых процессов. Среда разработки на каждом шагу подсказывает вам, то что вы могли бы забыть и что вы могли бы применить. Открывает вам список методов и интерфейсов и тут же дает краткое справочное описание...

C# имеет ещё одно преимущество (правда я всех удобств не опишу, их довольно весомая кипа для определённых задач) которое мне очень нравиться это LINQ запрос к любому массиву данных. Будь то, простой массив строк, будь то массив XML, будь то массив SQL данных и так далее массив чего-то тип данных которого содержит интерфейс перечисления.

Короче самое важное, это то что на .NET мы пишем меньше и яснее чем на VC++, делаем всё быстро и видим в коде меньше лишнего, а документация нам подсказывает очень многое, что писать самим нам не надо, а использовать уже готовое. В голове мы сразу строим то, что дольше будем строить на VC++. Идеальный современный вариант для решения прикладных задач как для новичков, так и для профессионалов различных областей.

Ходят много споров о том какая программа C# или VC++ работает быстрее. Если вас интересует моё мнение, то я скажу, что скорость следует обсуждать тогда, когда её нужно обсуждать. В наших задачах скорость работы программ не плохая, даже хорошая. Пока скажу только одно. При настройках по умолчанию при первом запуске .программы написанной на .NET, JIT-компилятор компилирует CIL код ("код языка .NET, аналогичный ассемблеру") в машинный код и это занимает время. А далее если этот код будет работать постоянно на приемлемой скорости...

------------

На cheatengine форуме я нашёл тему, в которой парень хочет научиться C# и не знает с чего начать и ему посоветовали написать программу решающую прикладную простую задачу показывать знаки зодиака по введенной информации.

Я написал эту программу остальное оставляю желающим исправить. Весь написанный код нужно расположить в Program.cs - генерированном файле при создании консольного приложения

post-3-1299491697,68_thumb.png

Тоже самое для того чтобы скопировать в проект:


using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Zodiak
{
class Program
{
const string InfoZodiak = @"
Овен March 21 April 20
Телец April 21 May 20
Близнецы May 21 June 21
Рак June 22 July 22
Лев July 23 August 23
Дева August 24 September 23
Весы September 24 October 23
Скорпион October 24 November 22
Стрелец November 23 December 21
Козерог December 22 January 20
Водолей January 21 February 20
Рыбы February 21 March 20";


static DateTime SpecialParsing(int year, string mount, string day)
{
// Шаблон 2008-11-01
string tempString = String.Format("{0}-{1}-{2}", year, mount, day);
return DateTime.Parse(tempString);
}

public static string GetZodiakName(DateTime current)
{
//если дата больше 22 декабря и не больше 21 января, то это козерог
if (
(current.Month == 12 && current.Day >= 22 && current.Day <= 31) ||
(current.Month == 1 && current.Day <= 20)
)
return "Козерог";

string pattern = @"\b(\w+?)\s*\b(\w+?)\s*\b(\w+?)\s*\b(\w+?)\s*\b(\w+?)\s*\b";
foreach (Match match in Regex.Matches(InfoZodiak, pattern,
RegexOptions.IgnoreCase))
{
string nameZodiak = match.Groups[1].Value; // Aries
string leftMounth = match.Groups[2].Value; // March
string leftDay = match.Groups[3].Value; // 21
string rigthMounth = match.Groups[4].Value; // April
string rigthDay = match.Groups[5].Value; // 19

var leftDate = SpecialParsing(current.Year, leftMounth, leftDay);
var rigthDate = SpecialParsing(current.Year, rigthMounth, rigthDay);

if (leftDate <= current && current <= rigthDate)
return nameZodiak;
}
return "input error";
}

static int GetUserInputToInt(string message)
{
Console.WriteLine(message);
return int.Parse(Console.ReadLine());
}

static void Main()
{
Console.WriteLine("Введите дату и я покажу знак зодикака");
Console.WriteLine("");
// Test it input is "April 18" (output "Aries") and "April 21" (output "Taurus")
do
{
try
{
// Получить от пользователя значение даты
int mounth, day, year = DateTime.Now.Year;
mounth = GetUserInputToInt("Введите месяц (числом):");
day = GetUserInputToInt("Введите день:");
var date = new DateTime(year, mounth, day);

// Вывести знак зодика
string sign = GetZodiakName(date);
Console.WriteLine("Знак зодиака: {0}", sign);
Console.WriteLine("");
}
catch { continue; }

// Желает ли пользователь закончить работу с программой
Console.WriteLine("Выйти? (Y или NO):");
string questionExit = Console.ReadLine();
if (string.Compare(questionExit, "Y", true) == 0)
return;
Console.WriteLine("");

} while (true);
}
}
}
using System;

Вводим числом месяц, дату и получаем ответ. Здесь использовались регулярные выражения, работа с датами. Всё описывается в MSDN...

Может быть позже я напишу ещё что-нибудь.

Ссылка на комментарий
Поделиться на другие сайты

Кажется все-таки VC++ в плане скорости выигрывает. .NET вообще по сути своей - виртуальная машина. А еще могу посетовать на то, что девы забивают на оптимизацию, так как дедлайн заставляет выпускать продукт полу-сырым.

Ссылка на комментарий
Поделиться на другие сайты

Хочу обратить внимание на пример из  MSDN по удобной работе с LINQ запросами.

Представьте, что у нас есть список студентов (который можно удобно импортировать из различных баз данных, в том числе и xml...). Этот список в стиле C# описывается вот так:

           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}

Наша задача вывести два списка студентов с низким средним баллом и высоким

  Low averages

   Omelchenko, Svetlana:77.5

   O'Donnell, Claire:72.25

   Garcia, Cesar:75.5

  High averages

   Mortensen, Sven:93.5

   Garcia, Debra:88.25

Человек не знающий LINQ пошёл "ай да наяривать циклы"   :grin: чтобы вывести результат. А вот элегантное решение с LINQ:

        var booleanGroupQuery =
            from student in students
            group student by student.Scores.Average() >= 80; //pass or fail!

Ну и вот весь код


{
    // The element type of the data source.
    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }

    public static List<Student> GetStudents()
    {
        // Use a collection initializer to create the data source. Note that each element
        //  in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
        };

        return students;

    }

    static void Main()
    {
        List<Student> students = GetStudents();
        var booleanGroupQuery =
            from student in students
            group student by student.Scores.Average() >= 80; //pass or fail!

        foreach (var studentGroup in booleanGroupQuery)
        {
            Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
class GroupSample1

Ссылка на комментарий
Поделиться на другие сайты

Кратко о функциональных выражениях в C#.

Встретил я вот такой "условный" вариант и мне он понравился:

new [] { this.button1, this.button2, ... this.button7 }
        .ToList().ForEach(p => p.Click += button_Click);

void button_Click(object sender, EventArgs e)
{
        var btn = (Button) sender;
}

 А вот мой пример насколько может быть запутанных функциональное выражение. Запутанное в том смысле если к нему ещё не успел привыкнуть (не рекомендуется писать dSMultiLauncher, лучше DSMultiLauncher, но мне лень исправлять здесь):

 Вариант 1


var files = from v in dSMultiLauncher.Files
                 where (v.GroupID==Position)
                 select v.Puth;

foreach (var Puth in files)
{
     int imageIndex = iconListManager.AddFileIcon(Puth);
     conList_ListView.Items.Add(Puth, imageIndex);
}

           

Вариант 2

(from v in dSMultiLauncher.Files
             where (v.GroupID == Position)
             select v.Puth).ToList().ForEach(Puth =>{
                    int imageIndex = iconListManager.AddFileIcon(Puth);
                    iconList_ListView.Items.Add(Puth, imageIndex);});

                   

Не могу определить что лучше для меня, т.к. я привыка. к функциольным выражениям

Целая функция будет выглядеть так:

void FillGroupsAndIcons()
        {
            // Выбрать группу на которую установлен указатель в таблице
            int Position = groupBindingSource.Position;
            DSMultiLauncher.GroupRow gr = dSMultiLauncher.Group[Position];

            // Найти файлы по текущей группе и
            // заполнить иконками писок
            (from v in dSMultiLauncher.Files
             where (v.GroupID == gr.ID)
             select v.Puth).ToList().ForEach(Puth =>{
                    int imageIndex = iconListManager.AddFileIcon(Puth);
                    iconList_ListView.Items.Add(Puth, imageIndex);});
        }

Однако из предыдущего поста, где был пример  из MSDN, там foreach решили поставить вместо того чтобы писать длинное функциональное выражение. Короче, для большинства проще расписывать с foreach, да и скорость выполнения быстрее (где-то я об этом читал). Но с функциональными выражениями пишешь код словно составляешь длинное предложение, прикольно ))) 

Этот код я использую в реальном приложении, которое я пишу.

post-3-1291611520,66_thumb.png

Я пишу его для того чтобы разобраться как работать с сериализацией и десериализацией в базах данных, с автоманическим генерированием схемы базы данных и т.п..... Здесь приложение Windows Forms, но сериализация аналогична и в ASP.NET и SiliverLigth и т.п.. Например, можно будет на сервере генерировать разметку отправляемую клиенту. Она например будет нужна для записной книжки перетаскиваемых ссылок на различные медиафайлы... и этот список будет запоминаться на сервере... )

Ссылка на комментарий
Поделиться на другие сайты

  • 3 месяца спустя...

Не подскажете, как заменить строку вида

private CheckBox f00000d;

на что-нибудь более вразумительное

private CheckBox CheckBox1;

? Так, чтобы f00000d заменился во всём листинге, где он используется. Можно конечно использовать поиск и замену, но ведь есть риск заменить не то что гужно. Автозамена предусмотрена? Пользуюсь VS 2008.

Ссылка на комментарий
Поделиться на другие сайты

Я не знаю есть ли поддержка рефакторинга в 2008 версии. Но она есть в 2010.

1) В проекте не должно быть ошибок

2) Выделить изменяемое слово

3) Нажать на ctrl+R+R

4) Переименовать и согласиться.

В этом случае будет переименовывание там где надо. Очень удобно. Стоит почитать побольше про рефакториг и шаблоны для различных часто встречающихся кусках кода. Тогда скорость разработки увеличиться.

Ссылка на комментарий
Поделиться на другие сайты

Программа делающая следующее.

Показывает изменилась ли страница на сервере. Если да то уведомляет. Промжуточную информацию сохраняет в info.txt там же где расположен программа. Написал программу за 15 минут вместе с перерывами.

* 1) От сервера gamehacklab.ru ты забираешь ответ о размере страницы

* 2) Если размер получен в первый раз, то ты его запоминаешь на жёстком диске в создаваемый новый текстовый файл.

* 3) Если файл уже создан и из него можно прочитать размер, то ты его сравниваешь с полученным от сервера.

* 4) Если размеры не равные, то новый размер сохраняется в перезаписываемый файл и раздаётся оповещающий сигнал 2 раза.

* 5) Программа закрывается.

Исходник (привожу специально текстом, а не файлами):


using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Media;
using System.Threading;

namespace WhileGetInetInfo
{
class Program
{

static class FileOpearations
{
private const string filename = @"info.txt";

/// <summary>
/// Получить полный путь до файла информации из директории с программой
/// </summary>
/// <returns>Полный путь</returns>
private static string GetFullPathINFO()
{
return Environment.CurrentDirectory + "/" + filename;
}

/// <summary>
/// Получить прошлый размер
/// </summary>
/// <returns>Размер, если была ошибка, то возвращает ноль</returns>
public static long GetLastSize()
{
string PathINFO = GetFullPathINFO();
long size = 0;
try
{
using (StreamReader sr = new StreamReader(PathINFO))
size = long.Parse(sr.ReadLine());
}
catch { }
return size;
}

/// <summary>
/// Сохранить новый размер в файл
/// </summary>
/// <param name="size">Размер</param>
public static void SaveLastSize(long size)
{
string PathINFO = GetFullPathINFO();
using (StreamWriter sr = new StreamWriter(PathINFO))
sr.WriteLine(size.ToString());
}
}

static class ConnectionOpearations
{
private const string uri = @"http://gamehacklab.ru";

/// <summary>
/// Получить прошлый размер
/// </summary>
/// <returns>Размер, если была ошибка, то возвращает ноль</returns>
public static long GetLastSize()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);

request.MaximumAutomaticRedirections = 4;
request.MaximumResponseHeadersLength = 4;
request.Credentials = CredentialCache.DefaultCredentials;
request.Timeout = 10000;

long size = 0;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Stream receiveStream = response.GetResponseStream();
using (StreamReader readStream = new StreamReader(receiveStream))
size = readStream.ReadToEnd().Length;
}

return size;
}
}

static void ShowErrMess(string mess)
{
Console.WriteLine(mess);
}

static void Pause()
{
Console.Read();
}


static void Main(string[] args)
{
/*
* 1) От сервера ты забираешь ответ о размере страницы
* 2) Если размер получен в первый раз, то ты его запоминаешь на жёстком диске в создаваемый новый текстовый файл.
* 3) Если файл уже создан и из него можно прочитать размер, то ты его сравниваешь с полученным от сервера.
* 4) Если размеры не равные, то новый размер сохраняется в перезаписываемый файл и раздаётся оповещающий сигнал 2 раза.
* 5) Программа закрывается.
*/

long curSize = ConnectionOpearations.GetLastSize();
if (curSize==0)
{
ShowErrMess("Ошибка соединение с сервером");
Pause();
return;
}

long lastSize = FileOpearations.GetLastSize();
if (lastSize != curSize) // если к файлу невозможно обратиться или в нём нет записей
{
// То инициировать сообщение о том, что данные на серевере изменились
FileOpearations.SaveLastSize(curSize);
Console.WriteLine("Данные на сервере изменились!");

SoundPlayer sp = new SoundPlayer(Properties.Resources.FileCorrupted);
sp.Play();
Thread.Sleep(2000);
sp.Play();

Pause();
return;
}
}
}
}

using System;

GamehacklabInfo.rar

Ссылка на комментарий
Поделиться на другие сайты

  • 3 недели спустя...

Хочу написать о высокоточном измерении времени в C# и тестировании циклов

Прочтение этой информации позволит вам с высокой точностью определить время выполнения участка кода. Впрочем эта же информация лежит в закромах MSDN.

Всем известно, что .net программы работают медленнее C++. Напомню, что CIL-код .net приложения при первом запуске компилируется JIT компилятором в машинный код, т.е. в тот же код который мы привыкли видеть в дизассемблерах под WIndows. Но оптимизация самого CIL-кода по всему приложению и затем оптимизация с компиляцией в машинный код происходит не так хорошо как это делает компилятор C++. В связи с этим будет очень полезно раз и навсегда уяснить как мерить производительность.

Сегодня ради эксперимента я измерю производительность трёх циклов: for, foreach и цикл foreach функционального выражения. Все эти элементы будут делать схожие вещи.

Мы создадим массив повторяющихся слов и будем добавлять эти элементы в дерево просмотра.

Итак создадим кнопку и listview и textBox.

post-3-1302319717,73_thumb.png

Создадим автоматически обработчик события о нажатии кнопки.


private void buttonstart_Click(object sender, EventArgs e)
{
// тут будет разные код
}
------
------

Для начала узнаем интересные вещи:


private void buttonstart_Click(object sender, EventArgs e)
{
// 1. Для любопытства узнаем:

// 1.1) число тактов в секунду
double frequency = Stopwatch.Frequency;

// 1.2) таймер с точностью до КАКИХ наносекунд
double nan = 1000L * 1000L * 1000L;

double nanosecPerTick = nan / frequency;

/* 1.3) Основан ли Stopwatch на счетчике производительности с
* высоким разрешением? В противном случае IsHighResolution равно
* false, что указывает на то, что таймер Stopwatch зависит
* системного таймера.
*/
var testc = Stopwatch.IsHighResolution;

// Выведем сообщение
textBoxFrequency.Text =
String.Format("{0}; {1}; {2};", frequency, nanosecPerTick, testc);

// Я получил:
// "3005600000; 0,332712270428533; True"
}
------
------

Вот так мы получили данные о тактах.

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

Сначала напишем шаблон:


private void buttonstart_Click(object sender, EventArgs e)
{
var timePerParse = Stopwatch.StartNew();

timePerParse.Start(); // начали отсчёт времени и тактов

/// ====> здесь будем писать код

timePerParse.Stop(); // закончили отсчёт

textBoxFrequency.Text =
String.Format("{0}(секунд); {1} тактов; {2} мс;",
timePerParse.Elapsed,
timePerParse.ElapsedTicks,
timePerParse.ElapsedMilliseconds);
}
-----------------------------
-------------------------

Обратите внимание на место куда будем писать код. Дальше я буду приводить только вставляемый код.


// 1. Способ
string[] strings1 = Enumerable.Repeat("I like programming.", 5000).ToArray();
for (int i = 1; i < strings1.Count(); i++)
treeView1.Nodes.Add(strings1[i]);

// Парочка результатов
// 00:00:00.8888617(секунд); 2671562745 тактов; 888 мс;
// 00:00:00.8861877(секунд); 2663525844 тактов; 886 мс;

Давайте перепишем этот способ более правильно
// 1. Способ
string[] strings1 = Enumerable.Repeat("I like programming.", 5000).ToArray();
int count = strings1.Count();
for (int i = 1; i < count; i++)
treeView1.Nodes.Add(strings1[i]);

//Пара результатов (уже лучше на ~15 мс)
//00:00:00.8718157(секунд); 2620329444 тактов; 871 мс;
//00:00:00.8728242(секунд); 2623360617 тактов; 872 мс;

-----------------------------
-----------------------------



// 2. Способ
var strings2 = Enumerable.Repeat("I like programming.", 5000);
foreach (String str in strings2)
treeView1.Nodes.Add(str);

// Парочка результатов (этот вариант идентичен первому)
//00:00:00.8717746(секунд); 2620205919 тактов; 871 мс;
//00:00:00.8723999(секунд); 2622085362 тактов; 872 мс;

-----------------------------
-----------------------------


// 3. Способ
Enumerable.Repeat("I like programming.", 5000).ToList().ForEach(str =>
treeView1.Nodes.Add(str));

// Парочка результатов
// 00:00:00.8715870(секунд); 2619642168 тактов; 871 мс;
// 00:00:00.8734485(секунд); 2625236901 тактов; 873 мс;
-----------------------------
-----------------------------

К моему удивлению, все циклы практически оптимизированы одинакого и выполняются ~872 мс. Здесь важно было понять насколько отличны все три метода. Я покажу более оптимизированный код, вот каким он должен быть в конечном итоге.


treeView1.BeginUpdate();
Enumerable.Repeat("I like programming.", 5000).ToList().ForEach(str =>
treeView1.Nodes.Add(str));
treeView1.EndUpdate();

//00:00:00.1277900(секунд); 384085872 тактов; 127 мс;

Ну а тперь законченный исходник для проекта Windows Forms:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace LangCE
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void buttonstart_Click(object sender, EventArgs e)
{
var timePerParse = Stopwatch.StartNew();

timePerParse.Start(); // начали отсчёт времени и тактов

// 3. Способ
treeView1.BeginUpdate();
Enumerable.Repeat("I like programming.", 5000).ToList().ForEach(str =>
treeView1.Nodes.Add(str));
treeView1.EndUpdate();

timePerParse.Stop(); // закончили отсчёт

textBoxFrequency.Text =
String.Format("{0}(секунд); {1} тактов; {2} мс;",
timePerParse.Elapsed,
timePerParse.ElapsedTicks,
timePerParse.ElapsedMilliseconds);
}
}
}

Используемая информация:

Работа с IEnumerable<T>(позволяет использовать методы расширения LINQ в частности repeat, toList и т.п.)

Работа со Stopwatch

Работа с TreeView

Ну и весь MSDN

Выводы лично для меня:

Этот тест помог мне усомниться в тестах других товарищей, которые утверждали о том что самый лучший цикл это for в C#.

Ссылка на комментарий
Поделиться на другие сайты

  • 2 недели спустя...

Неплохой приёмчик,при создание приложение под Windows на VS C#.

Скрин из VSC# пример:

post-1568-1303653523_thumb.jpg

post-1568-1303653587,01_thumb.jpg

post-1568-1303653607,51_thumb.jpg

Сам код:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Trainer_2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{

}

private void timer1_Tick(object sender, EventArgs e)
{
System.Diagnostics.Process[] myprocess = System.Diagnostics.Process.GetProcessesByName("Quake4v1.0");
if (myprocess.Length != 0)
{
button1.Enabled = true;
}
else
{
button1.Enabled = false;
}
}

private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;

}
}
}

Ссылка на комментарий
Поделиться на другие сайты

  • 6 месяцев спустя...
×
×
  • Создать...

Важная информация

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