Condividi tramite


Troubleshooting Exceptions: System.InvalidOperationException

An InvalidOperationException is thrown when a method of an object is called when the state of the object cannot support the method call. The exception is also thrown when a method attempts to manipulate the UI from a thread that is not the main or UI thread.

Important

Because InvalidOperationExceptions can be thrown in a wide variety of circumstances, it's important to read and understand the Message that is displayed in the Exception Assistant.

In this article

A method running on a non-UI thread updates the UI

A statement in a foreach (For Each in Visual Basic) block changes the collection it is iterating

A Nullable<T> that is null is cast to T

A System.Linq.Enumerable method is called on an empty collection

Related articles

The code examples in this article show you how some common InvalidOperationException exceptions can occur in your app. How you handle the issues depends on the specific situation. If the exception is fatal to the functionality of your app, you might want to use a try … catch (Try .. Catch in Visual Basic) construct to capture the exception and clean up the app's state before you exit. But other InvalidOperationExceptions can be anticipated and avoided. The revised method examples show you some of these techniques.

A method running on a non-UI thread updates the UI

Causing an InvalidOperationException with a UI update from a non-UI thread | Avoiding InvalidOperationExceptions on non-UI threads

Most .NET GUI (graphical user interface) app frameworks, such as Windows Forms and Windows Presentation Foundation (WPF), let you access GUI objects only from the thread that creates and manages the UI (the Main or UI thread). An InvalidOperationException is thrown when you try to access a UI element from a thread that is not the UI thread.

Causing an InvalidOperationException with a UI update from a non-UI thread

Note

The following examples use the Task-based Asynchronous Pattern (TAP) to create non-UI threads. However, the examples are also relevant to all .NET Asynchronous Programming Patterns.

In these examples, the ThreadsExampleBtn_Click event handler calls the DoSomeWork method twice. The first call to the method (DoSomeWork(20); runs sychronously and succeeds. But the second call (Task.Run( () => { DoSomeWork(1000);});) runs on a thread in the app's thread pool. Because this call attempts to update the UI from a non-UI thread, the statement throws a InvalidOperationException

WPF and Store apps

Note

In Store Phone apps, an Exception is thrown instead of the more specific InvalidOperationException.

Exception messages:

WPF apps

Additional information: The calling thread cannot access this object because a different thread owns it.

Store apps

Additional information: The application called an interface that was marshalled for a different thread. (Exception from 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 apps

Exception message:

  • Additional information: Cross-thread operation not valid: Control 'TextBox1' accessed from a thread other than the thread it was created on.
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();
}

Back to topIn this articleIn this sectionIn this section

Avoiding InvalidOperationExceptions on non-UI threads

Windows UI frameworks implement a dispatcher pattern that includes a method to check whether a call to a member of a UI element is being executed on the UI thread, and other methods to schedule the call on the UI thread.

WPF apps

In WPF apps, use one of the Dispatcher.Invoke methods to excecute a delegate on the UI thread. If necessary, use the Dispatcher.CheckAccess method to determine if a method is running on a non-UI thread.

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 apps

In Windows Form apps, use the Control.Invoke method to excecute a delegate that updates the UI thread. If necessary, use the Control.InvokeRequired property to determine if a method is running on a non-UI thread.

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();
    }
}

Store apps

In Store apps, use the CoreDispatcher.RunAsync method to excecute a delegate that updates the UI thread. If necessary, use the HasThreadAccess property to determine if a method is running on a non-UI thread.

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;});
    }
}

Back to topIn this articleIn this sectionIn this section

A statement in a foreach (For Each in Visual Basic) block changes the collection it is iterating

Causing an InvalidOperationException with foreach | Avoiding InvalidOperationExceptions in loops

A foreach statement (For Each in Visual Basic) repeats a group of embedded statements for each element in an array or collection that implements the IEnumerable or IEnumerable interface. The foreach statement is used to iterate through the collection to read and modify the elements, but it can't be used to add or remove items from the collection. An InvalidOperationException is thrown if you add or remove items from the source collection in a foreach statement.

Causing an InvalidOperationException with foreach

In this example, an InvalidOperationException is thrown when the numList.Add(5); statement attempts to modify list in the foreach block.

Exception message:

  • Additional information: Collection was modified; enumeration operation may not execute.
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);
    }
}

Back to topIn this articleIn this sectionIn this section

Avoiding InvalidOperationExceptions in loops

Important

Adding or removing elements to a list while you are interating over the collection can have unintended and hard to predict side effects. If possible, you should move the operation outside of the iteration.

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);
    }
}

If your situation requires you to add or remove elements to a list as you iterate a collection, use a for (For in Visual Basic) loop:

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);
    }
}

Back to topIn this articleIn this sectionIn this section

A Nullable&lt;T&gt; that is null is cast to T

Causing an InvalidOperationException with an invalid cast | Avoiding InvalidOperationException from a bad cast

If you cast a Nullable structure that is null (Nothing in Visual Basic) to its underlying type, an InvalidOperationException exception is thrown.

Causing an InvalidOperationException with an invalid cast

In this method, an InvalidOperationException is thrown when the Select method casts a null element of dbQueryResults to an int.

Exception message:

  • Additional information: Nullable object must have a value.
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();
}

Back to topIn this articleIn this sectionIn this section

Avoiding InvalidOperationException from a bad cast

To avoid InvalidOperationException:

Nullable<T>.HasValue example

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 example

In this example, we use GetValueOrDefault(UTP) to specify a default that is outside of the expected values returned by 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();
}

Back to topIn this articleIn this sectionIn this section

A System.Linq.Enumerable method is called on an empty collection

The Enumerable methods Aggregate, Average, Last, Max, Min, First, Single, and SingleOrDefault perform operations on a sequence and return a single result.

Some overloads of these methods throw an InvalidOperationException exception when the sequence is empty (other overloads return null (Nothing in Visual Basic). SingleOrDefault also throws InvalidOperationException when the sequence contains more than one element.

Tip

Most of the Enumerable methods disussed in this section are overloaded. Be sure you understand the behavior of the overload that you choose.

Exception messages:

Aggregate, Average, Last, Max, and Min methods | First and FirstOrDefault methods | Single and SingleOrDefault methods

Aggregate, Average, Last, Max, and Min methods

  • Additional information: Sequence contains no elements

Causing an InvalidOperationException with Average

In this example, the following method throws an InvalidOperationException when it calls the Average method because the Linq expression returns a sequence that contains no elements that are greater than 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);
}

When you use one of these methods without checking for an empty sequence, you are implicitly assuming that the sequence is not empty, and that an empty sequence is an unexpected occurrence. Catching or throwing the exception is appropriate when you do assume that the sequence will be non-empty.

Avoiding an InvalidOperationException caused by an empty sequence

Use one of the Enumerable.Any methods to check if the sequence is empty.

Tip

Using Any can improve the performance of the check if the sequence to average might contain a large number of elements or if operation that generates the sequence is expensive.

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.");
    }
}

Back to topIn this articleIn this sectionIn this section

First and FirstOrDefault methods

First returns the first item in a sequence or throws an InvalidOperationException if the sequence is empty. You can call the FirstOrDefault method instead of First to return a specified or default value instead of throwing the exception.

Note

In the .NET Framework, types have a concept of default values. For example, for any reference type the default value is null, and for an integer type it is zero. See Default Values Table (C# Reference)

Causing an InvalidOperationException with First

The First is an optimization method that returns the first element in a sequence that satisfies a specified condition. If an element satisfying the condition isn't found, the methods throw an InvalidOperationException exception.

In this example, the First method throws the exception because dbQueryResults doesn't contain an element that is greater than 4.

Exception message:

  • Additional information: Sequence contains no matching element
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);

}

Avoiding an exception with FirstOrDefault

You can use one the FirstOrDefault methods in place of First to check that the firstNum sequence contains at least one element. If the query doesn't find an element satisfying the condition, it returns a specified or default value. You can check the returned value to determine if any elements are found.

Note

Using FirstOrDefault can be more complicated to implement if the default value of the type is a valid element in the sequence.

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.");
    }
}

Back to topIn this articleIn this sectionIn this section

Single and SingleOrDefault methods

The Enumerable.Single methods return the only element of a sequence, or the only element of a sequence that meets a specified test.

If there are no elements in the sequence, or there are more than one element in t he sequence, the metnod throws an InvalidOperationException exception.

You can use SingleOrDefault to return a specified or default value instead of throwing the exception when the sequence contains no elements. However, SingleOrDefault still throws an InvalidOperationException when the sequence contains more than one element that matches the selection predicate.

Note

In the .NET Framework, types have a concept of default values. For example, for any reference type the default value is null, and for an integer type it is zero. See Default Values Table (C# Reference)

Causing InvalidOperationExceptions with Single

In this example, singleObject throws an InvalidOperationException because dbQueryResults doesn't contain an element greater than 4.

Exception message:

  • Additional information: Sequence contains no matching element
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);
}

Causing InvalidOperationExceptions with Single or SingleOrDefault

In this example, singleObject throws an InvalidOperationException because dbQueryResults contains more than one element greater than 2.

Exception message:

  • Additional information: Sequence contains more than one matching element
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.");
    }
}

Avoiding InvalidOperationExceptions with Single

Using Single and SingleOrDefault also serves as documentation of your intentions. Single strongly implies that you expect one and only one result to be returned from the condition. SingleOrDefault declares that you expect one or zero results, but no more. When these conditions are invalid, throwing or catching the InvalidOperationException is appropriate. However, if you expect that invalid conditions will occur with some frequency, you should consider using other Enumerable methods, such as First or Where, to generate your results.

During development, you can use one of the Assert methods to check your assumptions. In this example, the highlighted code causes the debugger to break and displays an assert dialog box during development. The assert is removed in release code, and any Single will throw if the results are invalid.

Note

Using Take``1 and setting its count parameter to 2 limits the returned sequence to at most two elements. This sequence includes all the cases that you need to check (0, 1, and more than 1 elements) and can improve the performance of the check when the sequence might contain a large number of elements or if operation that generates the sequence is expensive.

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());
}

If you want to avoid the exception but still handle the invalid states in release code, you can modify the technique described above. In this example, the method responds to the number of elements returned by moreThan2 in the the switch statement.

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;
    }
}

Back to topIn this articleIn this sectionIn this section

Design Guidelines for Exceptions (.NET Framework Design Guidelines)

Handling and Throwing Exceptions (.NET Framework Application Essentials)

How to: Receive First-Chance Exception Notifications (.NET Framework Development Guide)

How to: Handle Exceptions in a PLINQ Query (.NET Framework Development Guide)

Exceptions in Managed Threads (.NET Framework Development Guide)

Exceptions and Exception Handling (C# Programming Guide)

Exception Handling Statements (C# Reference)

Try...Catch...Finally Statement (Visual Basic)

Exception Handling (F#)

Exceptions in C++/CLI

Exception Handling (Task Parallel Library)

Exception Handling (Debugging)

Walkthrough: Handling a Concurrency Exception (Accessing Data in Visual Studio)

How to: Handle Errors and Exceptions that Occur with Databinding (Windows Forms)

Handling exceptions in network apps (XAML) (Windows)

Back to topIn this article