Condividi tramite


Exception handling for Windows Runtime apps in C# or Visual Basic

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

Learn how to handle exceptions (or errors) in Windows Runtime apps using C# or Visual Basic. You can handle exceptions as Microsoft .NET exceptions with try-catch blocks in your app code, and you can process app-level exceptions by handling the Application.UnhandledException event.

Prerequisites

If you're new to .NET programming, you might want to read some more about exception handling in the common language runtime (CLR) and the .NET Framework. For more info, see these topics:

Debugging in Visual Studio

Many of the techniques that you probably use to debug your app rely on features of Microsoft Visual Studio. These features include first-chance exception handling, breakpoints, stepping through code, edit and continue, locals, watches, and remote debugging. These features, and techniques that use them, aren't documented extensively in this topic because most of them are common to any Visual Studio development or debugging scenario. For more info, see Debug Windows Runtime apps in Visual Studio.

Getting specific Windows Runtime error and exception info

The Windows Runtime uses a unified system that exposes the errors slightly differently for each language and programming model. .NET programmers generally use the term exception rather than error because, in its programming model, .NET represents the occurrence of an error as an exception object.

For an app written in C# or Visual Basic, the system exposes errors as an exception that is either an instance of the System.Exception class, or an exception that's derived from System.Exception. All ..NET exceptions have the inherited HResult and Message properties.

A Windows Runtime error can sometimes be mapped to a standard exception that is more specific, if that error is similar in meaning to a standard exception from the .NET core profile for Windows. That's why you might see the error appear as a derived exception class such as InvalidOperationException rather than as a System.Exception, either during debugging or in your handler code. The standard exception mapping is done internally by the .NET runtime, and is based on converting the original Windows Runtime error's HResult numeric value and returning a more specific standard exception to any listeners. (Listeners include first-chance exceptions for debuggers, or app code that's handling exceptions.)

Exceptions that can't be mapped to a standard exception for .NET are left as the System.Exception type. If you need more detail about what the exception means in context, read the Message string while you're debugging or catching exceptions. Every exception also has an HResult value. In some cases the HResult matches an existing system error code, even if it doesn't get mapped to a .NET standard exception.

If you're getting an exception for an error where you know the code but don't know how or whether it's being mapped as a standard exception, see Cross-reference: Standard exceptions and error codes for a table that may help you determine the correct exception type to catch.

Using try-catch

.NET supports a try-catch technique for isolating exceptions in code. (There are slight variations in the keywords that C# and Visual Basic use for this technique; for this discussion we'll use the C# keywords.) When an exception occurs in a try block, the Windows Runtime searches the associated catch blocks, in the order in which they appear in app code, until it locates one that handles the exception based on its type.

As you develop an app, you might have exception-handling code that isolates exceptions that you'll eventually eliminate from code because they're preventable. A useful technique for these is a try-catch coding style in combination with first-chance exception notification. While you're still fixing code or trying to isolate the causes of errors, put the app code that you suspect is throwing the exception within the try block. Then write a general catch block for the exception. While you're debugging that area of app code, use first-chance, watches, breakpoints, and other techniques in the Microsoft Visual Studio debugger, along with the Exception from the catch block, to figure out what's going wrong. Eventually, after you've addressed the coding error, you might not need that try-catch block anymore.

There are also app code error-handling scenarios that are a result of how some of the Windows Runtime API is designed. For these scenarios, you might still need try-catch blocks even in the production code for your app. For example, some of the Windows Runtime networking APIs and network information APIs are designed so that the API calls throw errors even when the app code uses the APIs correctly. The errors come from external systems, but reach your app as Windows Runtime exceptions that are thrown by the logic of the Windows Runtime API. The errors you get from external systems may be transient or rare, but you may still need to handle them in your app if you can't completely prevent the error from happening at run time.

If you catch the exception as the general System.Exception, that catch block will catch all exceptions, even the ones that are mapped to standard exceptions. That catch block would also catch exceptions you've thrown from your own app code. So if you want to mix the handling of standard exceptions, custom exceptions, and unmapped cases that you can catch only with System.Exception, place the block that catches System.Exception last in your sequence of catch blocks. That way, any more specific exception types are caught first, and the catch (Exception e) statement catches those that don't match your previous filters.

The .NET languages also support the use of the finally clause following try-catch. An advanced scenario where you might use catch and finally together is to obtain and use resources in a try block, deal with exceptional circumstances in a catch block, and release the resources in the finally block. For more info, see try-catch-finally in the C# Reference.

Note  Visual C++ component extensions (C++/CX) code can't use the finally keyword.

 

Remember, not all exceptions are recoverable. Some exceptions indicate that something is drastically wrong and the app won't be permitted to run any further. Even if you catch these in try-catch code, that might be the last code that your app gets to run before it's terminated by the Windows Runtime or the system. Even while debugging you might not get past such a severe exception as a first-chance exception, because the runtime won't let debugging continue. For more info about the design philosophy— that is, why some exceptions are considered recoverable and some aren't—see Handling and throwing exceptions.

Rethrowing exceptions

For some exceptions, you can't fix the error condition entirely in your app code but you do have more info about why that exception happens in a particular circumstance. For example, you might be catching a system error that you can associate with some aspect of your app's use of the Windows Runtime API, but the message in the original error is vague or isn't using HResult values that could have explained the circumstances better. If you're able to give more precise info about why that exception is happening, based on your knowledge of your app's logic in context, it's sometimes worthwhile to throw a new, more precise exception from within the catch block—that is, to "rethrow" the exception you caught. You might then want to catch the new exception at the app level (UnhandledException), so that the app-level logic can record the exception condition (in a log, for example) or otherwise save info about it as part of the app state. This might be useful app-state data for when your app needs to restart. Or you might just want other exception-recording techniques, such as external crash reporting, to see the more precise exception as thrown by your app code. Either way, when you rethrow an exception, anticipate that the app will eventually terminate but that it will do so while reporting the exception conditions from your rethrown exception rather than the original exception you caught.

App-level exception handling

Some exceptions, if they're not caught in your try-catch blocks or if you rethrow them from there, are propagated by the Windows Runtime as app-level exceptions. At this point you can use an app-level exception-handling technique and mark the exception occurrence as Handled in the event's data. If you don't mark the occurrence as Handled, the exception is propagated back to the Windows Runtime and system, and in most cases the app will then be terminated. Still, just before that happens, your handler for Application.UnhandledException can serve as a centralized, app-level exception handler to log errors, save what app state you can, and perform similar operations.

If you don't have a handler for UnhandledException and the UnhandledException event fires, the system terminates the app because the exception that motivated firing the UnhandledException event was unhandled. Similarly, if you do have a handler but never set the Handled property of the event data to true, the system terminates your app, usually just after the handler is invoked. If the UnhandledException event handler sets Handled to true, your app won't necessarily be terminated immediately and your app code can attempt to recover from the error condition by executing the remainder of your UnhandledException handler.

Your app might use an UnhandledException handler to take some action because you consider the source exception to be recoverable. The definition of "recoverable" depends at least partly on your app's logic, features or design. For an unrecoverable exception, your app might still perform logging, cleanup, or other actions before being terminated. But be aware that there are exceptions that will terminate your app before it even reaches your UnhandledException handler. Also, we recommend that you do not routinely set the value of Handled to true for all UnhandledException events and their originating error or exception cases, or routinely consider the circumstances as recoverable. Here's why:

  • Often the UnhandledException event handler doesn’t have enough info about that exception and its context to know whether it's safe to continue and run more app code. Your app code may be in an inconsistent state or could be getting incorrect data results from the system. This could result in subsequent failures that are even harder to recover from or to avoid in the future if the app continues to run.

  • The Windows Runtime and the system consider some exceptions to be unrecoverable regardless of the circumstances during which they're thrown, because the runtime execution code itself will be in an inconsistent state following these exceptions. If more unrecoverable exceptions happen, even if you set Handled to true, the app is still terminated. That might happen at a time that's no longer detectable (you might not have a try block that isolates it, or the system terminates the app without raising the UnhandledException event because it's a severe exception).

  • Tip  Even if you do set Handled to true, it's a good idea to save app state just in case you need it for a future restart.

     

At the app level, some of the specifics of the original exception may not longer be available after propagation through layers of system and app code. For any exception that really originated from your app's code, using a try-catch block in the app code is a better, more accurate exception-handling technique than using UnhandledException later on. Here are the reasons why:

  • The type, message, and stack trace for UnhandledException event data are not guaranteed to exactly match the original exception as it was available to try-catch. Internally, the Windows Runtime does a lot of work to unify the error reporting between systems and your app, but sometimes info just can't be retrieved from the app's context. In particular, in your app-level exception handling you might lose at least one of the possible stack traces that was available with the original exception (either managed or native stack, depending on the context and other circumstances).
  • The try-catch exception object might have an InnerException in nested exception cases, but the UnhandledException event arguments won't have this.
  • The Message property does copy the message of the originally raised exception, but that's not always enough for detailed debugging or run-time recovery.

Even with these limitations, the app-level exception handling technique can still be useful for situations where your app is consuming components or services. An exception at run time due to a missing service or bad code or data from a component might be recoverable, but you can't always wrap the connection points to your dependency in a try-catch block.

An UnhandledException event handler is a useful interim technique while you're still refining your code and isolating exceptions that are difficult to reproduce. For some of these exceptions you can eventually wrap a specific API call in a try-catch block. Or you can just fix the originating code if the exceptions turn out to be the result of code-usage mistakes.

It’s possible for the UnhandledException event to fire for exceptions that never have any chance of being caught by other app code or wrapped by try-catch blocks. For example, if the XAML framework is performing layout and an exception is raised, this exception won’t propagate through any app code because layout happens automatically. (A layout pass is not the direct result of any API that your app code calls; it's just one of the underlying systems that act when the app starts or resumes and XAML for UI is loaded.) In this case, the UnhandledException event fires, and your app-level handler will be the first point at which any app code is notified about the exception.

Note  When a debugger is attached—for example, while you're actively developing your app in the IDE—the original exceptions are usually caught as first-chance exceptions in the Visual Studio debugger. These include exceptions that you may not be able to wrap with try-catch. For typical debugging techniques combined with certain exception cases like this, your UnhandledException event handler might not be called while you're running your app with a debugger.

 

Exceptions and asynchronous programming

Asynchronous programming is a key tenet of the Windows Runtime. You probably won't get far in your app's programming without touching at least some of the feature areas that use async programming techniques extensively. For example, the file picker and programmatic file access APIs are almost all async methods. Many media or imaging APIs are also async methods.

You can make calls to async methods from within try blocks, but not from within a catch block. Exceptions that come from an async method call thus can't make another async method call as part of their recovery option. In other words, you're allowed only one level of async method calling if you've entered any error condition.

Most of the existing async methods that attempt to populate an object that you'll want later will use an IAsyncOperation return value. If it's a method you wrote yourself in .NET code, the equivalent type is Task<TResult>. The task can report whether it completed successfully or failed. That's usually more info for recovery than is available when you rely on exceptions reaching the app level. At the app level, all the caller has are the HResult and Message, and no connection to the original Task or operation and how or when it reported successful completion. For example, a task can report IsCanceled is true but the exception won't capture that detail.

Exceptions from components and libraries

Components or libraries can throw exceptions when you access their APIs. As with your own code or system code, the exception can sometimes be mapped as a standard exception when you catch it or when you're debugging. If that's the case, you can apply all the guidance you've read so far about exceptions in general. However, you may need to experiment a bit to see which of the exceptions that a component throws are still recoverable in your app, so you can decide how to handle them by means of a catch block or as a case in your UnhandledException event handler.

Your code could encounter a custom exception, depending on which components or libraries you use. A true custom exception can't be mapped to .NET standard exceptions; it always appears as a System.Exception type to your code if you attempt to catch it, see it as first-chance, or handle it at app level. A custom exception has an HResult numeric value and hopefully a useful Message. You might need documentation, headers, or other info sources from the author of the component or library to learn more about what the exception means and how to prevent it.

Tip  Sometimes converting the HRESULT value from integer to hexadecimal representations of the numeric error code is helpful for finding references to an HRESULT value in general documentation sources, such as the system error code reference. Even custom components sometimes choose to raise errors using the system-defined codes if their exception is a good match with the meaning of a previously-defined system error or exception. Or see Cross-reference: Standard exceptions and error codes.

 

If you're writing a Windows Runtime component yourself using a .NET language, how you throw exceptions is one of the many areas about which you'll need to learn the specific programming requirements. In particular, you're not permitted to create custom public System.Exception derivatives. (However, you can use the existing derivatives in the .NET for Windows Runtime apps APIs, or leave the class as private.) And you'll have to think about how other languages see your exceptions, because supporting multiple languages is often the reason for creating your own Windows Runtime component. For more info, see Creating Windows Runtime Components in C# and Visual Basic, especially the "Throwing exceptions" section.

.NET-specific exceptions for the Windows Runtime

For .NET programming, the Windows Runtime supports these exceptions that don't have equivalent standard exceptions in the other languages:

LayoutCycleException

LayoutCycleException is an exception that is thrown specifically by the layout and rendering subsystems of the Windows Runtime while they are composing a XAML-based layout. LayoutCycleException can only occur when your code (or code in a component you're using) calls the UIElement.Measure or UIElement.Arrange method. Those calls are typically made from within the ArrangeOverride and MeasureOverride method implementations of a control, a panel, or another element that performs a custom layout on its child elements.

The layout system has an inherent internal concept of passes over layout, during which the layout adjusts itself asynchronously. The layout logic is based on several inputs and a bottom-up evaluation of all contributing layout elements in a visual tree. In some situations it's possible for an app layout to introduce a loop or condition that causes the layout system to throw LayoutCycleException. In such a loop, the layout system can't arrive at a solution and can't produce the visuals for a finalized layout, because it repeatedly invalidates its candidate layouts. For example, there could be a layout-related property binding or custom property-changed handling between a parent and child that keeps invalidating the inner parts of the layout before it can finish the outer part.

A custom container doesn't necessarily have full control over what elements could exist within it, so loops like this aren't completely avoidable either by control authors or by app writers.

Unfortunately there's really no good run-time recovery option for a LayoutCycleException. If you're still in the development phase, and if it's your own code that throws the exception, have a close look at how your layout container changes its own layout-affecting properties as part of its layout overrides. The logic used there could be the cause of the loop. Otherwise, if contained elements are reporting bad info to an otherwise sound layout logic (the parent-child scenario mentioned previously), you might have to debug the app-specific consumption of that control to find out what's causing a LayoutCycleException in a particular layout situation. There might also be scenarios where you don't use a UI that threw a LayoutCycleException and you're able to completely replace the UI with a different XAML composition that doesn't have any layout issues.

If you're writing an app but getting a LayoutCycleException from a Windows Runtime UI class or from a third-party code base, have a look at how your elements are nested and at their object-property relationships. There might be objects at different levels of your visual tree that are attempting to share values through bindings or other techniques, where the values are changing dynamically at run time. These properties and their sources or dependencies could be the source of a looping layout invalidation.

Generally, don't throw a LayoutCycleException from your app code, not even from a layout-method override of a specific Panel or Control implementation. Leave it up to the system to detect the conditions and throw the exception.

Note  For more info on how panels work, see Custom panels overview.

 

XamlParseException

XamlParseException is thrown specifically by the XAML parser component of the Windows Runtime. It means that an object graph couldn't be successfully created from a XAML file or from a XAML string or fragment. If this exception is thrown for a XAML page that the app attempted to load for UI, no elements of that page will be available. The best you might do is attempt to use another page that does parse for your Window.Content property and root visual. If you get this exception from loading smaller units of XAML, such as run-time parse operations from XamlReader.Load, your app can catch this exception and probably recover from it by using other XAML that parses, or by using some other fallback strategy.

By the time you're finalizing the production code for your app, you shouldn't be getting XamlParseException anymore from any XAML that's packaged with your app. During the design phase, Visual Studio can report on many parsing errors before that XAML even loads and before the app code is activated. It reports these errors or exceptions in output windows, or by syntax marking in the XAML text editor, or in other areas of the IDE. The design-time reporting shortens the cycle of fixing errors because you don't need to build and run the app just to load XAML and see errors. The design-time experience can also report on multiple errors in succession rather than failing on only the first exception encountered in a given XAML construct.

A XamlParseException contains several important pieces of info that can help you correct issues even if the IDE doesn't provide a design-time experience for error reporting. The Message property of the exception gives the line number and the column position within the text line where the parsing error occurs. If a specific attribute or element is what failed to be parsed, the name of that attribute or element is in the Message string too. The named elements or attributes are for the specific error encountered, so if this is a first-chance exception you might only see an exception and related message for the first part of XAML that fails to parse, even though there might be additional errors further in. It's possible that fixing the error and reloading the XAML will reach another error that is lexically further into the same XAML construct. For severe markup errors, such as XAML that's completely malformed or invalid as XML, you might get a message that reports the error at line 0, position 0.

Note  If you're converting code from other frameworks or are generally familiar with this exception from having handled it in other frameworks, note that the Windows Runtime implementation is a bit different. There aren't discrete LineNumber and LinePosition properties in the specific event type. This info is all rolled up into an override of the common exception Message string.

 

Generally, don't have your Windows Runtime app code throw a XamlParseException that it creates and initiates. The exception should be reserved for use by full-featured XAML parsers such as the built-in parser for XAML, or for related facilities like the platform implementation of the XamlReader.Load API.

Tip  Switching your Visual Studio debugging mode to support mixed-mode debugging can be useful because it exposes the native stack. The native stack sometimes has clues about underlying code-definition problems that surface as a XAML issue. The managed stack doesn't always capture the originating class name for resource lookups or walk into the constructor code that fails, especially if the class definition comes from a component.

 

For more info on XAML parse exceptions, see XAML overview or Basic XAML syntax.

ElementNotAvailableException and ElementNotEnabledException

ElementNotAvailableException and ElementNotEnabledException aren't commonly used, so your app code won't typically have to handle these errors. Your app code might create, initialize, and throw these errors, though, specifically if you are implementing a custom automation peer for a custom control class. For more info, see Custom automation peers.

Displaying error info in the UI

In general, don't use the developer-oriented info from a System.Exception or any derivative directly in your app UI. For example, don't show the raw HRESULT code to the user. Although the Message string might contain info that helps you fix code while you're still developing the app, that string won't typically help the user avoid the underlying error whether you catch it or not. If you do catch the error, you might be able to interpret an error message and present a recovery path to the user, but do this in a way that the user can act upon. For example, you can catch the error without rethrowing it, and then offer the user a recovery option in your app's own UI. If your app is validating input that's passed to a service, and the service API throws an error on invalid input, show UI that enables the user to correct the input. Then you can have your code use the service API to try submitting the input again. If you don't have a good try-catch opportunity, at least write a case into your UnhandledException event handler that enables the user to correct the conditions, and that lets your app continue.

If you do show UI to users when errors occur, use inline page techniques rather than dialogs. Use dialogs only when the user has to correct an error condition that blocks most of the functionality of your app. For more info, see Guidelines and checklist for message dialogs.

If your app crashes, don't attempt to display any info to the user that describes the specifics of the crash even if the app lifetime state lets you do so. According to the guidelines for app suspend and resume, let the user see only the start screen on restart after any Windows Runtime app crash. When your app starts again, have it enable the user to resume what he or she was doing. If you used UnhandledException to write state info about your app before it was terminated, that info may be useful when restarting but not during the termination. Describing how to restore app state from recovered app data is beyond the scope of this topic; for more info about that, see Guidelines for app suspend and resume.

Exceptions in the other programming languages

  • For an app written in C++/CX, all errors are reported as a Platform::Exception object. Standard exceptions might be mapped to one of the other Platform namespace exceptions. Custom exceptions are declared as COMException objects with a custom HRESULT. You can't create classes based on Platform::Exception; the compiler will block any attempt to do so. For more info about exceptions in C++/CX, see Exceptions (C++/CX).
  • For an app written in JavaScript all errors are represented as a JavaScript error object and can be handled by function(error) definitions. Errors from asynchronous API calls can be processed using the then/done keywords.

Handling exceptions in network apps

There are some additional considerations for handling exceptions that use network APIs in the Windows.Networking.Sockets and Windows.Web.Http namespaces. For more info, see Handling exceptions in network apps.

System.Exception

Cross-reference: Standard exceptions and error codes

Exceptions (C++/CX)

Debug Windows Runtime apps in Visual Studio