Поделиться через


Разрешение вопросов, связанных с исключениями: System.InvalidOperationException

Исключение InvalidOperationException выдается, когда вызывается метод объекта в случае, если состояние объекта не поддерживает вызов метода. Исключение также выдается также в случае, когда метод пытается обработать пользовательский интерфейс из потока, не являющегося главным потоком или потоком пользовательского интерфейса.

Важно!

Так как исключения InvalidOperationException могут выдаваться в различных ситуациях, очень важно прочесть и понять сообщение Message, отображаемое в помощнике по исключениям.

Содержание этой статьи

Метод, выполняемый в потоке, не связанном с пользовательским интерфейсом, обновляет пользовательский интерфейс

Оператор в блоке foreach (For Each в Visual Basic) изменяет коллекцию, в которой он выполняет итерации

Nullable<T>, имеющий значение null, приводится к T

Метод System.Linq.Enumerable вызывается при пустой коллекции

Связанные статьи

В примерах кода в этой статье показано, как некоторые общие исключения InvalidOperationException могут появляться в вашем приложении. Способ обработки ошибок зависит от конкретной ситуации. Если исключение является неустранимым для функциональных возможностей приложения, то может потребоваться применение конструкции try … catch (Try .. Catch в Visual Basic) для захвата исключения и очистки состояния приложения перед выходом из него. Но, можно ожидать другие исключения InvalidOperationException и избегать их. В приведенных примерах методов показаны некоторые такие способы.

Метод, выполняемый в потоке, не связанном с пользовательским интерфейсом, обновляет пользовательский интерфейс

Вызов исключения InvalidOperationException с обновлением пользовательского интерфейса из потока без пользовательского интерфейса | Предотвращение исключения InvalidOperationException в потоках без пользовательского интерфейса

Большинство платформ приложений .NET GUI (графический интерфейс пользователя), например, Windows Forms и Windows Presentation Foundation (WPF) разрешают доступ к объектам графического интерфейса пользователя только из потока, который создает и управляет пользовательским интерфейсом (поток Main или UI). Исключение InvalidOperationException возникает при попытке доступа к элементу пользовательского интерфейса из потока, который не является потоком пользовательского интерфейса.

Вызов исключения InvalidOperationException с обновлением пользовательского интерфейса из потока без пользовательского интерфейса

Примечание

В следующем примере для создания потоков без пользовательского интерфейса используется Асинхронный шаблон, основанный на задачах (TAP).Однако приведенные примеры также относятся к .NET Шаблоны асинхронного программирования.

В этих примерах обработчик событий ThreadsExampleBtn_Click вызывает два раза метод DoSomeWork. Первый вызов метода (DoSomeWork(20); запускается синхронно и выполняется успешно. Но, второй вызов (Task.Run( () => { DoSomeWork(1000);});) запускается в потоке в пределах пула потоков. Так как этот вызов пытается обновить интерфейс пользователя из потока без пользовательского интерфейса, то оператор создает исключение InvalidOperationException

Приложения WPF и Магазина

Примечание

В приложениях магазина Phone возникает исключение Exception, а не более конкретное исключение InvalidOperationException.

Сообщения об исключениях:

Приложения WPF

Дополнительные сведения: вызывающий поток не может обратиться к этому объекту, так как им владеет другой поток.

Приложения Магазина

Дополнительные сведения: приложение вызвало интерфейс, который был маршалирован для другого потока. (Исключение из HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

private async void ThreadsExampleBtn_Click(object sender, RoutedEventArgs e)
{
    TextBox1.Text = String.Empty;

    TextBox1.Text = "Simulating work on UI thread.\n";
    DoSomeWork(20);

    TextBox1.Text += "Simulating work on non-UI thread.\n";
    await Task.Run(() => DoSomeWork(1000));

    TextBox1.Text += "ThreadsExampleBtn_Click completes. ";
}

private void DoSomeWork(int msOfWork)
{
    // simulate work
    var endTime = DateTime.Now.AddMilliseconds(msOfWork);
    while (DateTime.Now < endTime)
    {
        // spin
    };

    // report completion
    var msg = String.Format("Some work completed in {0} ms on UI thread. \n", msOfWork);
    TextBox1.Text += msg;
}

Приложения Windows Forms

Сообщение об исключении:

  • Дополнительные сведения: недопустимая операция в нескольких потоках: попытка доступа к элементу управления TextBox1 из потока, в котором он не был создан.
private async void ThreadsExampleBtn_Click(object sender, EventArgs e)
{
    TextBox1.Text = String.Empty;

    var tbLinesList = new List<string>() {"Simulating work on UI thread."};
    TextBox1.Lines = tbLinesList.ToArray();
    DoSomeWork(20, tbLinesList);

    tbLinesList.Add("Simulating work on non-UI thread.");
    TextBox1.Lines = tbLinesList.ToArray();
    await Task.Run(() => DoSomeWork(1000, tbLinesList));

    tbLinesList.Add("ThreadsExampleBtn_Click completes.");
    TextBox1.Lines = tbLinesList.ToArray();
}
private void DoSomeWork(int msOfWork, List<string> tbLinesList)
{
    // simulate work
    var endTime = DateTime.Now.AddMilliseconds(msOfWork);
    while (DateTime.Now < endTime) { };
    {
        // spin
    };

    // report completion
    var msg = String.Format("Some work completed in {0} ms on UI thread. \n", msOfWork);
    tbLinesList.Add(msg);
    TextBox1.Lines = tbLinesList.ToArray();
}

К началуВ этой статьеВ данном разделеВ этом разделе

Предотвращение исключения InvalidOperationException в потоках без пользовательского интерфейса

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

Приложения WPF

В приложениях WPF для выполнения делегата в потоке пользовательского интерфейса используйте один из методов Dispatcher.Invoke. При необходимости, для определения, выполняется ли метод в потоке без пользовательского интерфейса, используйте метод Dispatcher.CheckAccess.

private void DoSomeWork(int msOfWork)
{
    var endTime = DateTime.Now.AddMilliseconds(msOfWork);
    while (DateTime.Now < endTime)
    {
        // spin
    };

    // report completion
    var msgFormat = "Some work completed in {0} ms on {1}UI thread.\n";
    var msg = String.Empty;
    if (TextBox1.Dispatcher.CheckAccess())
    {
        msg = String.Format(msgFormat, msOfWork, String.Empty);
        TextBox1.Text += msg;
    }
    else
    {
        msg = String.Format(msgFormat, msOfWork, "non-");
        Action act = ()=> {TextBox1.Text += msg;};
        TextBox1.Dispatcher.Invoke(act);
    }
}

Приложения Windows Forms

В приложениях Windows Form для выполнения делегата, который обновляет поток пользовательского интерфейса, используйте метод Control.Invoke. При необходимости, для определения, выполняется ли метод в потоке без пользовательского интерфейса, используйте свойство Control.InvokeRequired.

private void DoSomeWork(int msOfWork, List<string> tbLinesList)
{
    // simulate work
    var endTime = DateTime.Now.AddMilliseconds(msOfWork);
    while (DateTime.Now < endTime)
    {
        // spin
    };

    // report completion
    var msgFormat = "Some work completed in {0} ms on {1}UI thread.\n";
    var msg = String.Empty;
    if (TextBox1.InvokeRequired)
    {
        msg = String.Format(msgFormat, msOfWork, "non-");
        tbLinesList.Add(msg);
        Action act = () => TextBox1.Lines = tbLinesList.ToArray();
        TextBox1.Invoke( act );
    }
    else
    {
        msg = String.Format(msgFormat, msOfWork, String.Empty);
        tbLinesList.Add(msg);
        TextBox1.Lines = tbLinesList.ToArray();
    }
}

Приложения Магазина

В приложениях Магазина для выполнения делегата, который обновляет поток пользовательского интерфейса, используйте метод CoreDispatcher.RunAsync. При необходимости, для определения, выполняется ли метод в потоке без пользовательского интерфейса, используйте свойство HasThreadAccess.

private void DoSomeWork(int msOfWork)
{
    // simulate work
    var endTime = DateTime.Now.AddMilliseconds(msOfWork);
    while (DateTime.Now < endTime)
    {
        // spin
    };

    // report completion
    var msgFormat = "Some work completed in {0} ms on {1}UI thread.\n";
    var msg = String.Empty;

    if (TextBox1.Dispatcher.HasThreadAccess)
    {
        msg = String.Format(msgFormat, msOfWork, String.Empty);
        TextBox1.Text += msg;
    }
    else
    {
        msg = String.Format(msgFormat, msOfWork, "non-");
        TextBox1.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,  
            ()=> {TextBox1.Text += msg;});
    }
}

К началуВ этой статьеВ данном разделеВ этом разделе

Оператор в блоке foreach (For Each в Visual Basic) изменяет коллекцию, в которой он выполняет итерации

Вызов исключения InvalidOperationException с оператором foreach | Предотвращение исключения InvalidOperationException в циклах

Оператор foreach (For Each в Visual Basic) повторяет группу вложенных операторов для каждого элемента массива или коллекции, которая реализует интерфейс IEnumerable или IEnumerable. Оператор foreach используется для итерации по коллекции для чтения и изменения элементов, но не может применяться для добавления или удаления элементов из коллекции. Исключение InvalidOperationException появляется в случае добавления или удаления элементов из исходной коллекции в операторе foreach.

Вызов исключения InvalidOperationException с оператором foreach

В этом примере появляется исключение InvalidOperationException, когда оператор numList.Add(5); пытается изменить список в блоке foreach.

Сообщение об исключении:

  • Дополнительные сведения: коллекция была изменена; невозможно выполнить операцию перечисления.
private void AddElementsToAList()
{
    var numList = new List<int>() { 1, 2, 3, 4 };

    foreach (var num in numList)
    {
        if (num == 2)
        {
            numList.Add(5);
        }
    }

    // Display list elements
    foreach (var num in numList)
    {
        Console.Write("{0} ", num);
    }
}

К началуВ этой статьеВ данном разделеВ этом разделе

Предотвращение исключения InvalidOperationException в циклах

Важно!

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

private void AddElementsToAList ()
{
    var numList = new List<int>() { 1, 2, 3, 4 };

    var numberOf2s = 0;

    foreach (var num in numList)
    {
        if (num == 2)
        {
            numberOf2s++;
        }
    }

    for (var i = 0; i < numberOf2s; i++ ) 
    { 
        numList.Add(5); 
    }

    // Display list elements
    foreach (var num in numList)
    {
        Console.Write("{0} ", num);
    }
}

Если вам необходимо добавить или удалить элементы из списка в ходе выполнения итерации в коллекции, используйте цикл for (For в Visual Basic):

private void AddElementsToAList ()
{
    var numList = new List<int>() { 1, 2, 3, 4 };

    for (var i = 0; i < numList.Count; i++) 
    {
        if (numList[i] == 2)
        {
            numList.Add(5);
        }
    }

    // Display list elements
    foreach (var num in numList)
    {
        Console.Write("{0} ", num);
    }
}

К началуВ этой статьеВ данном разделеВ этом разделе

Nullable<T>, имеющий значение null, приводится к T

Вызов исключения InvalidOperationException с недопустимым приведением | Предотвращение исключения InvalidOperationException из плохого приведения

Если вы приводите структуру Nullable, которая имеет значение null (Nothing в Visual Basic) к ее базовому типу, то создается исключение InvalidOperationException.

Вызов исключения InvalidOperationException с недопустимым приведением

В этом методе создается исключение InvalidOperationException, когда метод Select приводит элемент null в dbQueryResults к типу int.

Сообщение об исключении:

  • Дополнительные сведения: объект, допускающий значение null, должен иметь значение.
private void MapQueryResults()
{
    var dbQueryResults = new int?[] { 1, 2, null, 4 };

    var ormMap = dbQueryResults.Select(nullableInt => (int)nullableInt);

    //display map list
    foreach (var num in ormMap)
    {
        Console.Write("{0} ", num);
    }
    Console.WriteLine();
}

К началуВ этой статьеВ данном разделеВ этом разделе

Предотвращение исключения InvalidOperationException из плохого приведения

Для предотвращения исключения InvalidOperationException:

  • Используйте свойство Nullable.HasValue для выбора только тех элементов, которые не являются нулевыми.

  • Используйте один из перегруженных методов Nullable.GetValueOrDefault для назначения элементу null значения по умолчанию.

Пример значения Nullable<T>.HasValue

private void MapQueryResults()
{
    var dbQueryResults = new int?[] { 1, 2, null, 4 };

    var ormMap = dbQueryResults
        .Where(nulllableInt => nulllableInt.HasValue)
        .Select(num => (int)num);

    //display map list
    foreach (var num in ormMap)
    {
        Console.Write("{0} ", num);
    }
    Console.WriteLine();

    // handle nulls
    Console.WriteLine("{0} nulls encountered in dbQueryResults",
        dbQueryResults.Where(nullableInt => !nullableInt.HasValue).Count());
}

Пример значения Nullable<T>.GetValueOrDefault

В этом примере метод GetValueOrDefault(UTP) используется для задания значения по умолчанию, которое находится за пределами ожидаемых значений, возвращаемых операцией dbQueryResults.

private void MapQueryResults()
{
    var dbQueryResults = new int?[] { 1, 2, null, 4 };
    var nullFlag = int.MinValue;

    var ormMap = dbQueryResults.Select(nullableInt => nullableInt.GetValueOrDefault(nullFlag));

    // handle nulls
    Console.WriteLine("'{0}' indicates a null database value.", nullFlag);

    foreach (var num in ormMap)
    {
        Console.Write("{0} ", num);
    }
    Console.WriteLine();
}

К началуВ этой статьеВ данном разделеВ этом разделе

Метод System.Linq.Enumerable вызывается при пустой коллекции

Методы Enumerable, Aggregate, Average, Last, Max, Min, First, Single и SingleOrDefault выполняют операции над последовательностью и возвращают один результат.

Некоторые перегрузки этих методов создают исключение InvalidOperationException, если последовательность является пустой (другие перегрузки возвращают значение null (Nothing в Visual Basic). SingleOrDefault также создает исключение InvalidOperationException, если последовательность содержит несколько элементов.

Совет

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

Сообщения об исключениях:

Методы Aggregate, Average, Last, Max и Min | Методы First и FirstOrDefault | Методы Single и SingleOrDefault

Методы Aggregate, Average, Last, Max и Min

  • Дополнительные сведения: последовательность не содержит элементов

Вызов исключения InvalidOperationException с помощью метода Average

В этом примере следующий метод создает исключение InvalidOperationException при вызове метода Average, так как выражение Linq возвращает последовательность, не содержащую элементы, которые больше 4.

private void FindAverageOfNumbersGreaterThan4()
{
    var dbQueryResults = new[] { 1, 2, 3, 4 };

    var avg = dbQueryResults.Where(num => num > 4).Average();

    Console.WriteLine("The average value numbers greater than 4 in the returned records is {0}", avg);
}

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

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

Для проверки, является ли последовательность пустой, используйте один из методов Enumerable.Any.

Совет

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

private void FindAverageOfNumbersGreaterThan4()
{
    var dbQueryResults = new[] { 1, 2, 3, 4 };

    var moreThan4 = dbQueryResults.Where(num => num > 4);

    if(moreThan4.Any())
    {
        var msgFormat = "The average value numbers greater than 4 in the returned records is {0}";
        Console.WriteLine(msgFormat, moreThan4.Average());
    }
    else
    {
        // handle empty collection 
        Console.WriteLine("There are no values greater than 4 in the ecords.");
    }
}

К началуВ этой статьеВ данном разделеВ этом разделе

Методы First и FirstOrDefault

Выражение First возвращает первый элемент последовательности или создает исключение InvalidOperationException, если последовательность является пустой. Для возвращения заданного значения или значения по умолчанию вместо создания исключения можно вызывать метод FirstOrDefault вместо метода First.

Примечание

На платформе .NET Framework типы имеют концепцию значений по умолчанию.Например, для любого ссылочного типа значением по умолчанию является null, а значением по умолчанию для целочисленного типа является нуль.См. раздел Таблица значений по умолчанию (Справочник по C#).

Вызов исключения InvalidOperationException с помощью метода First

First — это метод оптимизации, возвращающий первый элемент последовательности, который удовлетворяет заданному условию. Если элемент, удовлетворяющий условию, не будет найден, то методы создают исключение InvalidOperationException.

В этом примере метод First создает исключение, так как dbQueryResults не содержит элемент, который больше, чем 4.

Сообщение об исключении:

  • Дополнительные сведения: последовательность не содержит совпадающие элементы
private void FindANumbersGreaterThan4()
{
    var dbQueryResults = new[] { 1, 2, 3, 4 };

    var firstNum = dbQueryResults.First(n=> n > 4);

    var msgFormat = "{0} is an element of dbQueryResults that is greater than 4";
    Console.WriteLine(msgFormat, firstNum);

}

Предотвращение исключения с помощью FirstOrDefault

Для проверки, что последовательность firstNum содержит хотя бы один элемент, можно использовать один из методов FirstOrDefault вместо метода First. Если запрос не находит элемент, удовлетворяющий условию, он возвращает заданное значение или значение по умолчанию. Чтобы определить, обнаружены ли какие-либо элементы, можно проверить возвращаемое значение.

Примечание

Процедура реализации метода FirstOrDefault может быть более сложной, если значением по умолчанию для типа является допустимый элемент в последовательности.

private void FindANumbersGreaterThan4()
{
    var dbQueryResults = new[] { 1, 2, 3, 4 };

    // default(object) == null
    var firstNum = dbQueryResults.FirstOrDefault(n => n > 4);

    if (firstNum != 0)
    {
        var msgFormat = "{0} is an element of dbQueryResults that is greater than 4";
        Console.WriteLine(msgFormat, firstNum);
    }
    else
    {
        // handle default case
        Console.WriteLine("No element of dbQueryResults is greater than 4.");
    }
}

К началуВ этой статьеВ данном разделеВ этом разделе

Методы Single и SingleOrDefault

Методы Enumerable.Single возвращают единственный элемент последовательности или единственный элемент последовательности, который удовлетворяет заданному тесту.

Если в последовательности элементы отсутствуют, или имеется несколько элементов, то метод создает исключение InvalidOperationException.

Для возвращения заданного значения или значения по умолчанию, вместо создания исключения, если последовательность не содержит элементов, можно использовать метод SingleOrDefault. Однако метод SingleOrDefault по-прежнему создает исключение InvalidOperationException, если последовательность содержит несколько элементов, удовлетворяющего предикату выбора.

Примечание

На платформе .NET Framework типы имеют концепцию значений по умолчанию.Например, для любого ссылочного типа значением по умолчанию является null, а значением по умолчанию для целочисленного типа является нуль.См. раздел Таблица значений по умолчанию (Справочник по C#).

Вызов исключений InvalidOperationException с помощью метода Single

В этом примере метод singleObject создает исключение InvalidOperationException, так как dbQueryResults не содержит элемент, который больше 4.

Сообщение об исключении:

  • Дополнительные сведения: последовательность не содержит совпадающие элементы
private void FindTheOnlyNumberGreaterThan4()
{
    var dbQueryResults = new[] { (object)1, (object)2, (object)3, (object)4 };

    var singleObject = dbQueryResults.Single(obj => (int)obj > 4);

    // display results
    var msgFormat = "{0} is the only element of dbQueryResults that is greater than 4";
    Console.WriteLine(msgFormat, singleObject);
}

Вызов исключений InvalidOperationException с помощью метода Single или SingleOrDefault

В этом примере метод singleObject создает исключение InvalidOperationException, так как dbQueryResults содержит несколько элементов, которые больше 2.

Сообщение об исключении:

  • Дополнительные сведения: последовательность содержит несколько совпадающих элементов
private void FindTheOnlyNumberGreaterThan2()
{
    var dbQueryResults = new[] { (object)1, (object)2, (object)3, (object)4 };

    var singleObject = dbQueryResults.SingleOrDefault(obj => (int)obj > 2);

    if (singleObject != null)
    {
        var msgFormat = "{0} is the only element of dbQueryResults that is greater than 2";
        Console.WriteLine(msgFormat, singleObject);
    }
    else
    {
        // handle empty collection
        Console.WriteLine("No element of dbQueryResults is greater than 2.");
    }
}

Предотвращение исключений InvalidOperationException с помощью метода Single

Методы Single и SingleOrDefault также можно использовать для обозначения свои намерений. Single строго подразумевает, что из условия предполагается возвращение только одного результата. SingleOrDefault объявляет, что ожидается один результат или нулевые результаты, но не более того. При несоблюдении этих условий может происходить создание или перехват исключения InvalidOperationException. Тем не менее, если предполагается, что недопустимые условия будут происходить с некоторой частотой, для формирования результатов следует использовать другие методы Enumerable, например, First или Where.

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

Примечание

Если применяется метод Take``1, а для его параметра count установлено значение 2, то возвращаемая последовательность будет ограничена, по крайней мере, двумя элементами.Эта последовательность включает в себя все случаи, которые необходимо проверить (0, 1 и свыше 1 элемента), и может повысить производительность проверки, когда последовательность может содержать большое количество элементов, или если операция, создающая последовательность, является дорогостоящей.

private void FindTheOnlyNumberGreaterThan4()
{
    var dbQueryResults = new[] { (object)1, (object)2, (object)3, (object)4 };
    var moreThan4 = dbQueryResults.Where(obj => (int)obj > 4).Take(2);

    System.Diagnostics.Debug.Assert(moreThan4.Count() == 1, 
        String.Format("moreThan4.Count() == 1; Actual count: {0}", moreThan4.Count()));

    // do not handle exceptions in release code
    Console.WriteLine("{0} is the only element of dbQueryResults that is greater than 4", 
        moreThan4.Single());
}

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

private void FindTheOnlyNumberGreaterThan2()
{
    var dbQueryResults = new[] { (object)1, (object)2, (object)3, (object)4 };

    var moreThan2 = dbQueryResults.TakeWhile(obj => (int)obj > 2).Take(2);

    switch(moreThan2.Count())
    { 
        case 1:
            // success
            var msgFormat = "{0} is the only element of dbQueryResults that is greater than 2";
            Console.WriteLine(msgFormat, moreThan2.Single());
            break;
        case 0:
            // handle empty collection
            Console.WriteLine("No element of the dbQueryResults are greater than 4.");
            break;
        default: // count > 1
            // handle more than one element
            Console.WriteLine("More than one element of dbQueryResults is greater than 4");
            break;
    }
}

К началуВ этой статьеВ данном разделеВ этом разделе

Связанные статьи

Правила разработки исключений (Разработка на .NET Framework)

Обработка и создание исключений (Основные сведения о приложениях .NET Framework)

Практическое руководство. Получение уведомлений о первичном исключении (Руководство по разработке .NET Framework)

Практическое руководство. Обработка исключений в запросе PLINQ (Руководство по разработке .NET Framework)

Исключения в управляемых потоках (Руководство по разработке .NET Framework)

Исключения и обработка исключений (Руководство по программированию на C#)

Операторы обработки исключений (Справочник по C#)

Оператор Try...Catch...Finally (Visual Basic)

Обработка исключений (F#)

Исключения в C++/CLI

Обработка исключений (Библиотека параллельных задач)

Обработка исключений (Отладка)

Пошаговое руководство. Обработка исключения параллелизма (Доступ к данным в Visual Studio)

Практическое руководство. Обработка ошибок и исключений, происходящих при связывании данных (Windows Forms)

Обработка исключений в сетевых приложениях (XAML) (Windows)

К началуСодержание этой статьи