疑難排解例外狀況:System.InvalidOperationException
呼叫物件的方法時,若物件的狀態無法支援方法呼叫,會擲回 InvalidOperationException。 方法嘗試從非主執行緒或 UI 執行緒的執行緒操作 UI 時,也會擲回例外狀況。
重要
因為 可能在各種不同的情況下擲回 InvalidOperationException,所以請務必閱讀並了解例外狀況助理中所顯示的 Message。
本文內容
在非 UI 執行緒上執行以更新 UI 的方法
變更其逐一查看的集合之 foreach (Visual Basic 中的 For Each) 區塊中的陳述式
將 Null 轉換成 T 的 Nullable<T>
在空集合上呼叫的 System.Linq.Enumerable 方法
相關文章
本文中的程式碼範例,示範一些會在您的應用程式中發生的常見 InvalidOperationException 例外狀況。 處理問題的方式需視特定情況而定。 如果對應用程式功能而言是嚴重錯誤的例外狀況,您可能要使用 try … catch (Visual Basic 中的 Try Catch) 建構函式來擷取例外狀況,並清除應用程式的狀態,再結束應用程式。 但可以預測並避免其他 InvalidOperationException。 修改過的方法範例會示範一些此類技術。
在非 UI 執行緒上執行以更新 UI 的方法
從非 UI 執行緒更新 UI 造成 InvalidOperationException | 避免非 UI 執行緒上的 InvalidOperationExceptions
大部分 .NET GUI (圖形化使用者介面) 應用程式架構,例如 Windows Form 和 Windows Presentation Foundation (WPF),可讓您只從建立及管理 UI 的執行緒 (主要或 UI 執行緒) 存取 GUI 物件。 嘗試從非 UI 執行緒存取 UI 元素時,會擲回 InvalidOperationException。
從非 UI 執行緒更新 UI 造成 InvalidOperationException
注意事項 |
---|
下列範例使用 以工作為基礎的非同步模式 (TAP) 建立非 UI 執行緒。不過,這些範例也與所有.NET 非同步程式設計模式 相關。 |
在這些範例中,ThreadsExampleBtn_Click 事件處理常式會呼叫 DoSomeWork 方法兩次。 第一次呼叫的方法 (DoSomeWork(20); 會同步執行並成功。 但第二次呼叫 (Task.Run( () => { DoSomeWork(1000);});) 會在應用程式執行緒集區中的執行緒上執行。 由於這個呼叫會嘗試從非 UI 執行緒更新 UI,所以此陳述式擲回 InvalidOperationException。
WPF 和市集應用程式
注意事項 |
---|
在手機的市集應用程式中,會擲回 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();
}
本文內容本節內容
避免非 UI 執行緒上的 InvalidOperationExceptions
Windows UI 架構實作了「發送器」(dispatcher) 模式,其中包含檢查是否正在 UI 執行緒上執行對 UI 元素成員呼叫的方法,以及在 UI 執行緒上排定呼叫的其他方法。
WPF 應用程式
在 WPF 應用程式中,使用其中一個 Dispatcher.Invoke 方法來執行 UI 執行緒上的委派。 如果有必要,請使用 Dispatcher.CheckAccess 方法,以判斷某個方法是否執行於非 UI 執行緒上。
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 方法來執行更新 UI 執行緒的委派。 如果有必要,請使用 Control.InvokeRequired 屬性,以判斷某個方法是否執行於非 UI 執行緒上。
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 方法來執行更新 UI 執行緒的委派。 如果有必要,請使用 HasThreadAccess 屬性,以判斷某個方法是否執行於非 UI 執行緒上。
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 (Visual Basic 中的 For Each) 區塊中的陳述式
使用 foreach 造成 InvalidOperationException | 避免迴圈中的 InvalidOperationExceptions
Foreach 陳述式 (Visual Basic 中的 For Each),會為陣列或集合中實作 IEnumerable 或 IEnumerable 介面的每個元素,重複內嵌的陳述式群組。 foreach 陳述式可用來逐一查看集合,以讀取和修改元素,但它不能用來新增或移除集合中的元素。 如果您在 foreach 陳述式中,新增或移除來源集合的元素,會擲回 InvalidOperationException。
使用 foreach 造成 InvalidOperationException
在這個範例中,numList.Add(5); 陳述式嘗試修改 foreach 區塊中的清單時,就會擲回 InvalidOperationException。
例外狀況訊息:
- 其他資訊: 集合已修改; 列舉操作可能無法執行。
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);
}
}
本文內容本節內容
避免迴圈中的 InvalidOperationExceptions
重要
逐一查看集合時,如果對清單新增或移除元素,可能會有非預期且很難預測的副作用。如果可能的話,您應該移動反覆運算之外的作業。
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 (Visual Basic 中的 For) 迴圈:
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);
}
}
本文內容本節內容
將 Null 轉換成 T 的 Nullable<T>
無效的轉換造成 InvalidOperationException | 從錯誤的轉換避免 InvalidOperationException
如果您轉換的 Nullable 結構,是將 null (Visual Basic 中的 Nothing) 轉為其基礎類型,會擲回 InvalidOperationException 例外狀況。
無效的轉換造成 InvalidOperationException
在這個方法中,Select 方法將 dbQueryResults 的 Null 元素轉換為 int 時,會擲回 InvalidOperationException。
例外狀況訊息:
- 其他資訊: 可為 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 屬性,以選取那些不是 Null 的元素。
使用其中一種多載的 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 (Visual Basic 中的 Nothing)。 若序列包含一個以上的元素,SingleOrDefault 也會擲回 InvalidOperationException。
提示
本節中討論的大部分 Enumerable 方法都是多載。請確定您了解所選擇之多載的行為。
例外狀況訊息:
Aggregate、Average、Last、Max 和 Min 方法 | First 和 FirstOrDefault 方法 | Single 和 SingleOrDefault 方法
Aggregate、Average、Last、Max 和 Min 方法
- 其他資訊: 序列未包含元素
使用 Average 造成 InvalidOperationException
在這個範例中,下列方法會在呼叫 Average 方法時,擲回 InvalidOperationException,因為 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 方法,檢查序列是否是空的。
提示
如果 Average 的序列可能包含大量元素,或如果產生序列的作業是高度耗費資源,則使用 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# 參考) |
使用 First 造成 InvalidOperationException
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 避免例外狀況
您可以使用其中一個 FirstOrDefault 方法,取代 First,以檢查 firstNum 序列是否包含至少一個元素。 如果查詢中找不到滿足條件的元素,它會傳回指定值或預設值。 您可以檢查傳回的值,以判斷是否找到任何元素。
注意事項 |
---|
如果類型的預設值在序列中是有效的元素,使用 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# 參考) |
使用 Single 造成 InvalidOperationExceptions
在這個範例中,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);
}
使用 Single 或 SingleOrDefault 造成 InvalidOperationExceptions
在這個範例中,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.");
}
}
使用 Single 避免 InvalidOperationExceptions
使用 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());
}
如果您想要避免此例外狀況,同時處理發行程式碼中的無效狀態,可以修改上述的技巧。 在這個範例中,這個方法會回應 switch 陳述式中,moreThan2 所傳回的元素數目。
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 應用程式基本功能) (機器翻譯)
如何:接收 First-Chance 例外狀況通知 (.NET Framework 開發指南) (機器翻譯)
如何:處理 PLINQ 查詢中的例外狀況 (.NET Framework 開發指南) (機器翻譯)
Managed 執行緒中的例外狀況 (.NET Framework 開發指南) (機器翻譯)
例外狀況與例外狀況處理 (C# 程式設計指南) (機器翻譯)
例外狀況處理陳述式 (C# 參考) (機器翻譯)
Try...Catch...Finally 陳述式 (Visual Basic) (機器翻譯)
例外狀況處理 (F#) (機器翻譯)
C++/CLI 中的例外狀況 (機器翻譯)
例外狀況處理 (工作平行程式庫) (機器翻譯)
例外狀況處理 (偵錯) (機器翻譯)
逐步解說:處理並行例外狀況 (在 Visual Studio 中存取資料) (機器翻譯)
如何:處理資料繫結時所發生的錯誤和例外狀況 (Windows Forms) (機器翻譯)
處理網路應用程式中的例外狀況 (XAML) (Windows)
本文內容