Исключения и обработка исключений (руководство по программированию в c#)exceptions and exception handling (c# programming guide)

Обеспечение доступности данных об исключении при удаленном выполнении кодаEnsure that exception data is available when code executes remotely

При создании пользовательских исключений следует обеспечить доступность метаданных исключений для удаленно исполняемого кода.When you create user-defined exceptions, ensure that the metadata for the exceptions is available to code that is executing remotely.

Например, для реализаций .NET, которые поддерживают домены приложений, могут возникать исключения для этих доменов.For example, on .NET implementations that support App Domains, exceptions may occur across App domains. Предположим, что домен приложения А создает домен приложения В, который выполняет код, вызывающий исключение.Suppose App Domain A creates App Domain B, which executes code that throws an exception. Чтобы домен приложения A правильно перехватил и обработал исключение, он должен найти сборку, которая содержит исключение, порожденное доменом приложения B. Если домен приложения B порождает исключение, содержащееся в сборке в его базовой папке приложения, но не в базовой папке приложения домена A, то домен приложения A не сможет найти исключение и среда CLR породит исключение FileNotFoundException.For App Domain A to properly catch and handle the exception, it must be able to find the assembly that contains the exception thrown by App Domain B. If App Domain B throws an exception that is contained in an assembly under its application base, but not under App Domain A’s application base, App Domain A will not be able to find the exception, and the common language runtime will throw a FileNotFoundException exception. Чтобы избежать такой ситуации, можно развернуть сборку, содержащую сведения об исключении, двумя способами:To avoid this situation, you can deploy the assembly that contains the exception information in two ways:

  • Поместите эту сборку в общую базу приложения, совместно используемую обоими доменами приложений.Put the assembly into a common application base shared by both app domains.

    — или — or —

  • Если у этих доменов нет общей базы приложения, то подпишите сборку, содержащую сведения об исключении, строгим именем и разверните ее в глобальном кэше сборок.If the domains do not share a common application base, sign the assembly that contains the exception information with a strong name and deploy the assembly into the global assembly cache.

Общие сведения об исключенияхExceptions Overview

Исключения имеют следующие свойства.Exceptions have the following properties:

  • Исключения представляют собой типы, производные в конечном счете от .Exceptions are types that all ultimately derive from .
  • Используйте блок для выполнения таких инструкций, которые могут создавать исключения.Use a block around the statements that might throw exceptions.
  • Когда внутри такого блока возникает исключение, поток управления переходит к первому подходящему обработчику исключений в стеке вызовов.Once an exception occurs in the block, the flow of control jumps to the first associated exception handler that is present anywhere in the call stack. В C# ключевое слово обозначает обработчик исключений.In C#, the keyword is used to define an exception handler.
  • Если для созданного исключения не существует обработчиков, выполнение программы прекращается с сообщением об ошибке.If no exception handler for a given exception is present, the program stops executing with an error message.
  • Не перехватывайте исключение, если вы не намерены его обрабатывать с сохранением известного состояния приложения.Do not catch an exception unless you can handle it and leave the application in a known state. Если вы перехватываете , создайте его заново в конце блока , используя ключевое слово .If you catch , re-throw it using the keyword at the end of the block.
  • Если блок определяет переменную исключения, ее можно использовать для получения дополнительных сведений о типе созданного исключения.If a block defines an exception variable, you can use it to obtain more information about the type of exception that occurred.
  • Программа может явным образом создавать исключения с помощью ключевого слова .Exceptions can be explicitly generated by a program by using the keyword.
  • Объекты исключения содержат подробные сведения об ошибке, например состояние стека вызовов и текстовое описание ошибки.Exception objects contain detailed information about the error, such as the state of the call stack and a text description of the error.
  • Код в блоке выполняется даже в том случае, если создано исключение.Code in a block is executed even if an exception is thrown. Используйте блок , чтобы высвободить ресурсы, например закрыть потоки и файлы, которые были открыты внутри блока .Use a block to release resources, for example to close any streams or files that were opened in the block.
  • Управляемые исключения реализованы в платформе .NET на основе структурированного механизма обработки исключений Win32.Managed exceptions in .NET are implemented on top of the Win32 structured exception handling mechanism. Дополнительные сведения см. в статьях Structured Exception Handling (C/C++) (Структурированная обработка исключений в C и C++) и A Crash Course on the Depths of Win32 Structured Exception Handling (Интенсивное погружение в структурированную обработку исключений на платформе Win32).For more information, see Structured Exception Handling (C/C++) and A Crash Course on the Depths of Win32 Structured Exception Handling.

try…catch…finally

Wait, that’s not all.

The construct may have one more code clause: .

If it exists, it runs in all cases:

  • after , if there were no errors,
  • after , if there were errors.

The extended syntax looks like this:

Try running this code:

The code has two ways of execution:

  1. If you answer “Yes” to “Make an error?”, then .
  2. If you say “No”, then .

The clause is often used when we start doing something and want to finalize it in any case of outcome.

For instance, we want to measure the time that a Fibonacci numbers function takes. Naturally, we can start measuring before it runs and finish afterwards. But what if there’s an error during the function call? In particular, the implementation of in the code below returns an error for negative or non-integer numbers.

The clause is a great place to finish the measurements no matter what.

Here guarantees that the time will be measured correctly in both situations – in case of a successful execution of and in case of an error in it:

You can check by running the code with entering into – it executes normally, after . And then enter – there will be an immediate error, and the execution will take . Both measurements are done correctly.

In other words, the function may finish with or , that doesn’t matter. The clause executes in both cases.

Variables are local inside

Please note that and variables in the code above are declared before .

Otherwise, if we declared in block, it would only be visible inside of it.

and

The clause works for any exit from . That includes an explicit .

In the example below, there’s a in . In this case, is executed just before the control returns to the outer code.

The construct, without clause, is also useful. We apply it when we don’t want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized.

In the code above, an error inside always falls out, because there’s no . But works before the execution flow leaves the function.

Обработчики всех типов исключений

А теперь загадка: «Функции могут генерировать исключения любого типа данных, и, если исключение не поймано, это приведет к раскручиванию стека и потенциальному завершению выполнения всей программы. Поскольку мы можем вызывать функции, не зная их реализации (и, следовательно, какие исключения они могут генерировать), то как мы можем это предотвратить?».

К счастью, язык C++ предоставляет нам механизм обнаружения/обработки всех типов исключений — обработчик catch-all. Обработчик catch-all работает так же, как и обычный блок catch, за исключением того, что вместо обработки исключений определенного типа данных, он использует эллипсис () в качестве типа данных.

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

#include <iostream>

int main()
{
try
{
throw 7; // выбрасывается исключение типа int
}
catch (double a)
{
std::cout << «We caught an exception of type double: » << a << ‘\n’;
}
catch (…) // обработчик catch-all
{
std::cout << «We caught an exception of an undetermined type!\n»;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include <iostream>

intmain()

{

try

{

throw7;// выбрасывается исключение типа int

}

catch(doublea)

{

std::cout<<«We caught an exception of type double: «<<a<<‘\n’;

}

catch(…)// обработчик catch-all

{

std::cout<<«We caught an exception of an undetermined type!\n»;

}

}

Поскольку для типа int не существует специального обработчика catch, то обработчик catch-all ловит это исключение. Следовательно, результат:

Обработчик catch-all должен находиться последним в цепочке блоков catch. Это делается для того, чтобы исключения сначала могли быть пойманы обработчиками catch, адаптированными к конкретным типам данных (если они вообще существуют). В Visual Studio это контролируется, насчет других компиляторов — не уверен, есть ли такое ограничение.

Часто блок обработчика catch-all оставляют пустым:

catch(…) {} // игнорируются любые непредвиденные исключения

1 catch(…){}// игнорируются любые непредвиденные исключения

Этот обработчик ловит любые непредвиденные исключения и предотвращает раскручивание стека (и, следовательно, потенциальное завершение выполнения всей программы), но здесь он не выполняет никакой обработки исключений.

Пример 2

class Nest{
   public static void main(String args[]){
	 //Parent try block
     try{
    	//Child try block1
         try{
            System.out.println("Inside block1");
            int b =45/0;
            System.out.println(b);
         }
         catch(ArithmeticException e1){
            System.out.println("Exception: e1");
         }
         //Child try block2
         try{
            System.out.println("Inside block2");
            int b =45/0;
            System.out.println(b);
         }
         catch(ArrayIndexOutOfBoundsException e2){
            System.out.println("Exception: e2");
         }
        System.out.println("Just other statement");
    }
    catch(ArithmeticException e3){
    	 System.out.println("Arithmetic Exception");
         System.out.println("Inside parent try catch block");
    }
    catch(ArrayIndexOutOfBoundsException e4){
    	System.out.println("ArrayIndexOutOfBoundsException");
         System.out.println("Inside parent try catch block");
    }
    catch(Exception e5){
    	System.out.println("Exception");
         System.out.println("Inside parent try catch block");
    }
    System.out.println("Next statement..");
  }
}

Выход:

Inside block1
Exception: e1
Inside block2
Arithmetic Exception
Inside parent try catch block
Next statement..

Это еще один пример, который показывает, как работает вложенный блок try. Вы можете видеть, что есть два блока try-catch внутри тела основного try. Мы пометили их как блок 1 и блок 2 в приведенном выше примере.

  • Block1: мы разделили целое число на ноль, и это вызвало ArithmeticException, поскольку block1 обрабатывает ArithmeticException, отображается «Exception: e1».
  • Block2: В блоке 2 возникла ArithmeticException, но catch здесь обрабатывает только ArrayIndexOutOfBoundsException, поэтому в этом случае элемент управления переходит к основному телу try-catch (parent) и проверяет обработчик захвата ArithmeticException в родительских catch. Так как перехват родительского блока try обрабатывает это исключение с помощью универсального обработчика исключений, в качестве вывода отображается сообщение «Внутри родительского блока try catch».
  • Попытка родительского контроля Catch: здесь не возникло никаких исключений, поэтому на экране появилось сообщение «Next Statement ..»

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

Оцени статью

Оценить

Средняя оценка / 5. Количество голосов:

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

Спасибо за ваши отзыв!

КомпонентыParts

ТерминTerm ОпределениеDefinition
Необязательный элемент.Optional. Операторы, в которых может возникнуть ошибка.Statement(s) where an error can occur. Может быть составным оператором.Can be a compound statement.
Необязательный элемент.Optional. Разрешено несколько блоков.Multiple blocks permitted. Если при обработке блока возникает исключение, Каждая инструкция проверяется в текстовом порядке, чтобы определить, обрабатывается ли исключение, и представляет исключение, которое было выдано.If an exception occurs when processing the block, each statement is examined in textual order to determine whether it handles the exception, with representing the exception that has been thrown.
Необязательный элемент.Optional. Любое имя переменной.Any variable name. Начальное значение  — это значение возникшей ошибки.The initial value of is the value of the thrown error. Используется с для указания перехваченной ошибки.Used with to specify the error caught. Если этот параметр опущен, инструкция перехватывает любое исключение.If omitted, the statement catches any exception.
Необязательный элемент.Optional. Указывает тип фильтра класса.Specifies the type of class filter. Если значение относится к типу, заданному параметром или производного типа, идентификатор привязывается к объекту исключения.If the value of is of the type specified by or of a derived type, the identifier becomes bound to the exception object.
Необязательный элемент.Optional. Оператор с предложением перехватывает исключения только в том случае , если значение равно .A statement with a clause catches exceptions only when evaluates to . Предложение применяется только после проверки типа исключения и может ссылаться на идентификатор, представляющий исключение.A clause is applied only after checking the type of the exception, and may refer to the identifier representing the exception.
Необязательный элемент.Optional. Должен быть неявно преобразуемым в .Must be implicitly convertible to . Любое выражение, описывающее универсальный фильтр.Any expression that describes a generic filter. Обычно используется для фильтрации по номеру ошибки.Typically used to filter by error number. Используется с ключевым словом для указания обстоятельств, при которых ошибка перехвачена.Used with keyword to specify circumstances under which the error is caught.
Необязательный элемент.Optional. Инструкции для управления ошибками, происходящими в связанном блоке.Statement(s) to handle errors that occur in the associated block. Может быть составным оператором.Can be a compound statement.
Необязательный элемент.Optional. Ключевое слово, которое нарушает структуру.Keyword that breaks out of the structure. Выполнение возобновляется с помощью кода, непосредственно следующего за оператором.Execution resumes with the code immediately following the statement. Инструкция будет по-прежнему выполняться.The statement will still be executed. Не допускается в блоках.Not allowed in blocks.
Необязательный элемент.Optional. Блок всегда выполняется, когда выполнение покидает любую часть инструкции.A block is always executed when execution leaves any part of the statement.
Необязательный элемент.Optional. Инструкции, которые выполняются после возникновения всех других ошибок обработки.Statement(s) that are executed after all other error processing has occurred.
Завершает структуру.Terminates the structure.

Проброс исключения

В примере выше мы использовали для обработки некорректных данных. А что, если в блоке возникнет другая неожиданная ошибка? Например, программная (неопределённая переменная) или какая-то ещё, а не ошибка, связанная с некорректными данными.

Пример:

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

В нашем случае предназначен для выявления ошибок, связанных с некорректными данными. Но по своей природе получает все свои ошибки из . Здесь он получает неожиданную ошибку, но всё также показывает то же самое сообщение . Это неправильно и затрудняет отладку кода.

К счастью, мы можем выяснить, какую ошибку мы получили, например, по её свойству :

Есть простое правило:

Блок должен обрабатывать только те ошибки, которые ему известны, и «пробрасывать» все остальные.

Техника «проброс исключения» выглядит так:

  1. Блок получает все ошибки.
  2. В блоке мы анализируем объект ошибки .
  3. Если мы не знаем как её обработать, тогда делаем .

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

Ошибка в строке из блока «выпадает наружу» и может быть поймана другой внешней конструкцией (если есть), или «убьёт» скрипт.

Таким образом, блок фактически обрабатывает только те ошибки, с которыми он знает, как справляться, и пропускает остальные.

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

Здесь знает только, как обработать , тогда как внешний блок знает, как обработать всё.

Обработка некритических ошибок

У нас есть два способа определения последующих действий при ‘Non-Terminating Errors’. Это правило можно задать локально и глобально (в рамках сессии). Мы сможем полностью остановить работу скрипта или вообще отменить вывод ошибок.

Приоритет ошибок с $ErrorActionPreference

Еще одна встроенная переменная в Powershell $ErrorActionPreference глобально определяет что должно случится, если у нас появится обычная ошибка. По умолчанию это значение равно ‘Continue’, что значит «вывести информацию об ошибке и продолжить работу»:

Если мы поменяем значение этой переменной на ‘Stop’, то поведение скриптов и команд будет аналогично критичным ошибкам. Вы можете убедиться в этом на прошлом скрипте с неверным именем процесса:

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

Ниже значение, которые мы можем установить в переменной $ErrorActionPreference:

  • Continue — вывод ошибки и продолжение работы;
  • Inquire — приостановит работу скрипта и спросит о дальнейших действиях;
  • SilentlyContinue — скрипт продолжит свою работу без вывода ошибок;
  • Stop — остановка скрипта при первой ошибке.

Самый частый параметр, который мне приходится использовать — SilentlyContinue:

Использование параметра ErrorAction

Переменная $ErrorActionPreference указывает глобальный приоритет, но мы можем определить такую логику в рамках команды с параметром ErrorAction. Этот параметр имеет больший приоритет чем $ErrorActionPreference. В следующем примере, глобальная переменная определяет полную остановку скрипта, а в параметр ErrorAction говорит «не выводить ошибок и продолжить работу»:

Кроме ‘SilentlyContinue’ мы можем указывать те же параметры, что и в переменной $ErrorActionPreference. 

Значение Stop, в обоих случаях, делает ошибку критической.

Исключения в асинхронных методахExceptions in async methods

Асинхронный метод помечается модификатором async и обычно содержит одно или несколько выражений или инструкций await.An async method is marked by an async modifier and usually contains one or more await expressions or statements. Выражение await применяет оператор await к Task или Task<TResult>.An await expression applies the await operator to a Task or Task<TResult>.

Когда управление достигает в асинхронном методе, выполнение метода приостанавливается до завершения выполнения ожидающей задачи.When control reaches an in the async method, progress in the method is suspended until the awaited task completes. После завершения задачи выполнение в методе может быть возобновлено.When the task is complete, execution can resume in the method. Дополнительные сведения см. в разделе Асинхронное программирование с использованием ключевых слов async и await.For more information, see Asynchronous programming with async and await.

Завершенная задача, к которой применяется , может находиться в состоянии сбоя из-за необработанного исключения в методе, который возвращает эту задачу.The completed task to which is applied might be in a faulted state because of an unhandled exception in the method that returns the task. Ожидание задачи вызывает исключение.Awaiting the task throws an exception. Задача также может завершиться в отмененном состоянии, если отменяется асинхронный процесс, возвращающий эту задачу.A task can also end up in a canceled state if the asynchronous process that returns it is canceled. Ожидание отмененной задачи вызывает .Awaiting a canceled task throws an .

Для перехвата исключения ожидайте задачу в блоке и перехватывайте это исключение в соответствующем блоке .To catch the exception, await the task in a block, and catch the exception in the associated block. См. пример в разделе .For an example, see the section.

Задача может быть в состоянии сбоя, если в ожидаемом асинхронном методе произошло несколько исключений.A task can be in a faulted state because multiple exceptions occurred in the awaited async method. Например, задача может быть результатом вызова метода Task.WhenAll.For example, the task might be the result of a call to Task.WhenAll. При ожидании такой задачи перехватывается только одно из исключений и невозможно предсказать, какое исключение будет перехвачено.When you await such a task, only one of the exceptions is caught, and you can’t predict which exception will be caught. См. пример в разделе .For an example, see the section.

Обработка общих условий без выдачи исключенийHandle common conditions without throwing exceptions

Для условий, которые могут возникнуть, но способны вызвать исключение, рекомендуется реализовать обработку таким способом, который позволит избежать исключения.For conditions that are likely to occur but might trigger an exception, consider handling them in a way that will avoid the exception. Например, при попытке закрыть уже закрытое подключение возникает .For example, if you try to close a connection that is already closed, you’ll get an . Этого можно избежать, используя оператор для проверки состояния подключения перед попыткой закрыть его.You can avoid that by using an statement to check the connection state before trying to close it.

Если состояние подключения перед закрытием не проверяется, исключение можно перехватить.If you don’t check connection state before closing, you can catch the exception.

Выбор конкретного способа зависит от того, насколько часто ожидается возникновение данного события.The method to choose depends on how often you expect the event to occur.

  • Используйте обработку исключений, если событие не происходит очень часто, то есть если событие носит действительно исключительный характер и указывает на ошибку (например, в случае неожиданного конца файла).Use exception handling if the event doesn’t occur very often, that is, if the event is truly exceptional and indicates an error (such as an unexpected end-of-file). При использовании обработки исключений в обычных условиях выполняется меньше кода.When you use exception handling, less code is executed in normal conditions.

  • Если событие происходит регулярно в рамках нормальной работы программы, выполняйте проверку на наличие ошибок прямо в коде.Check for error conditions in code if the event happens routinely and could be considered part of normal execution. Проверка на наличие распространенных условий ошибки позволяет выполнять меньший объем кода благодаря устранению исключений.When you check for common error conditions, less code is executed because you avoid exceptions.

КомментарииRemarks

Если предполагается, что конкретное исключение может возникнуть во время определенного раздела кода, разместите код в блоке и используйте блок для удержания управления и обработайте исключение, если оно происходит.If you expect that a particular exception might occur during a particular section of code, put the code in a block and use a block to retain control and handle the exception if it occurs.

Оператор состоит из блока, за которым следует одно или несколько предложений, которые задают обработчики для различных исключений.A statement consists of a block followed by one or more clauses, which specify handlers for various exceptions. При возникновении исключения в блоке Visual Basic ищет инструкцию, которая обрабатывает исключение.When an exception is thrown in a block, Visual Basic looks for the statement that handles the exception. Если соответствующий оператор не найден, Visual Basic проверяет метод, который вызвал текущий метод, и т. д. в стеке вызовов.If a matching statement is not found, Visual Basic examines the method that called the current method, and so on up the call stack. Если блок не найден, Visual Basic отображает пользователю сообщение о необработанном исключении и останавливает выполнение программы.If no block is found, Visual Basic displays an unhandled exception message to the user and stops execution of the program.

В операторе можно использовать более одной инструкции .You can use more than one statement in a statement. В этом случае порядок предложений важен, так как они анализируются по порядку.If you do this, the order of the clauses is significant because they are examined in order. Перехватывайте более конкретные исключения перед менее конкретными.Catch the more specific exceptions before the less specific ones.

Следующие условия инструкции являются наименее конкретными, и будут перехватывать все исключения, производные от Exception класса.The following statement conditions are the least specific, and will catch all exceptions that derive from the Exception class. Обычно следует использовать один из этих вариантов в качестве последнего блока в структуре после перехвата всех конкретных исключений.You should ordinarily use one of these variations as the last block in the structure, after catching all the specific exceptions you expect. Поток управления никогда не может достичь блока, который следует за одним из этих вариантов.Control flow can never reach a block that follows either of these variations.

  • Например :The is , for example:

  • Оператор не имеет переменной, например:The statement has no variable, for example:

Если инструкция вложена в другой блок, Visual Basic сначала проверяет каждую инструкцию во внутреннем блоке.When a statement is nested in another block, Visual Basic first examines each statement in the innermost block. Если соответствующий оператор не найден, поиск продолжается до инструкций внешнего блока.If no matching statement is found, the search proceeds to the statements of the outer block.

Локальные переменные из блока недоступны в блоке, так как они являются отдельными блоками.Local variables from a block are not available in a block because they are separate blocks. Если вы хотите использовать переменную более чем в одном блоке, объявите переменную за пределами структуры.If you want to use a variable in more than one block, declare the variable outside the structure.

Совет

Инструкция доступна в виде фрагмента кода IntelliSense.The statement is available as an IntelliSense code snippet. В диспетчере фрагментов кода разверните узел шаблоны кода — Если для каждого, попробуйте перехватить, свойство и т. д., а затем — Обработка ошибок (исключения).In the Code Snippets Manager, expand Code Patterns — If, For Each, Try Catch, Property, etc, and then Error Handling (Exceptions). Дополнительные сведения см. в статье Фрагменты кода.For more information, see Code Snippets.

More Examples

Example

This example examines input. If the value is wrong,
an exception (err) is thrown.

The exception (err) is caught by the catch statement and a custom error message is displayed:

<!DOCTYPE html><html><body><p>Please input a number between
5 and 10:</p><input id=»demo» type=»text»><button type=»button»
onclick=»myFunction()»>Test Input</button><p id=»message»></p>
<script>function myFunction() {  var message, x;  message =
document.getElementById(«message»);  message.innerHTML = «»;
  x =
document.getElementById(«demo»).value; 
try {    
if(x == «») throw «is Empty»;   
if(isNaN(x)) throw «not a number»;   
if(x > 10) throw «too high»;   
if(x < 5) throw «too low»;  }  catch(err) {    message.innerHTML =
«Input » + err;  }}</script></body></html>

Example

The finally statement lets you execute code, after try and
catch, regardless of the result:

function myFunction()  var message, x;  message =
document.getElementById(«message»);  message.innerHTML = «»;
  x =
document.getElementById(«demo»).value;  try {    
if(x == «») throw «Empty»;    if(isNaN(x))
throw «Not a number»;    if(x >
10) throw «Too high»;    if(x <
5) throw «Too low»;  }  catch(err)
{    message.innerHTML = «Error: » +
err + «.»;  }  finally {    document.getElementById(«demo»).value = «»;
  }}

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector