Solución de problemas de excepciones: System.InvalidOperationException
Si se llama a un método de un objeto cuando el estado del objeto no admite la llamada de método, se lanza una InvalidOperationException. También se lanza la excepción cuando un método trata de manipular la interfaz de usuario desde un subproceso que no es el principal ni el subproceso de la interfaz de usuario.
Importante
Dado que las excepciones InvalidOperationException se pueden lanzar en una gran variedad de circunstancias, es importante que lea y comprenda el Message que se muestra en el Asistente de excepciones.
En este artículo
Un método que se ejecuta en un subproceso ajeno a la interfaz de usuario actualiza la interfaz de usuario
Una declaración de un bloque de foreach (For Each en Visual Basic) cambia la colección que está iterando
Un Nullable<T> que es NULL se convierte en T
Se llama a un método System.Linq.Enumerable en una colección vacía
Artículos relacionados
En los ejemplos de código de este artículo, se muestra cómo se pueden producir algunas excepciones InvalidOperationException comunes en la aplicación. La manera de controlar los problemas depende de cada situación en concreto. Si la excepción es irrecuperable para la funcionalidad de la aplicación, puede que le convenga usar una construcción try … catch (Try .. Catch en Visual Basic) para capturar la excepción y limpiar el estado de la aplicación antes de salir. Pero otras excepciones InvalidOperationException se pueden prever y evitar. Los ejemplos de métodos revisados le muestran algunas de estas técnicas.
Un método que se ejecuta en un subproceso ajeno a la interfaz de usuario actualiza la interfaz de usuario
Provocación de una excepción InvalidOperationException con una actualización de la interfaz de usuario desde un subproceso ajeno a la interfaz de usuario | Evitar excepciones InvalidOperationException en subprocesos ajenos a la interfaz de usuario
La mayoría de los marcos de aplicaciones de GUI (interfaz gráfica de usuario) de .NET, como Windows Forms y Windows Presentation Foundation (WPF), le permiten acceder a objetos de la GUI solo desde el subproceso que crea y administra la interfaz de usuario (el subproceso Main o UI). Si trata de acceder a un elemento de la interfaz de usuario desde un subproceso que no es el subproceso de la interfaz de usuario, se lanza una InvalidOperationException.
Provocación de una excepción InvalidOperationException con una actualización de la interfaz de usuario desde un subproceso ajeno a la interfaz de usuario
Nota
En los ejemplos siguientes, se utiliza el Modelo asincrónico basado en tareas (TAP) para crear subprocesos ajenos a la interfaz de usuario.Sin embargo, los ejemplos también son relevantes para todos los Modelos para la programación asincrónica de .NET.
En estos ejemplos, el controlador de eventos ThreadsExampleBtn_Click llama al método DoSomeWork dos veces. La primera llamada al método (DoSomeWork(20); se ejecuta de forma sincrónica correctamente. Pero la segunda llamada (Task.Run( () => { DoSomeWork(1000);});) se ejecuta en un subproceso del grupo de subprocesos de la aplicación. Como esta llamada trata de actualizar la interfaz de usuario desde un subproceso ajeno a la interfaz de usuario, la instrucción lanza unaInvalidOperationException.
Aplicaciones de la Tienda y WPF
Nota
En las aplicaciones del teléfono de la Tienda, se lanza una Exception en lugar de la InvalidOperationException, más específica.
Mensajes de excepción:
Aplicaciones de WPF |
Información adicional: el subproceso que realiza la llamada no puede acceder a este objeto porque el propietario es otro subproceso. |
Aplicaciones de la Tienda |
Información adicional: la aplicación llamó a una interfaz que se aplanó para un subproceso diferente. (Excepción de 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;
}
Aplicaciones de Windows Forms
Mensaje de excepción:
- Información adicional: operación no válida a través de subprocesos (se tuvo acceso al control "TextBox1" desde un subproceso distinto a aquel en que lo creó).
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();
}
En este artículoEn esta sección
Evitar excepciones InvalidOperationException en subprocesos ajenos a la interfaz de usuario
Los marcos de la interfaz de usuario de Windows implementan un patrón distribuidor que incluye un método para comprobar si se está ejecutando una llamada a un miembro de un elemento de interfaz de usuario en el subproceso de la interfaz de usuario, y otros métodos para programar la llamada en el subproceso de la interfaz de usuario.
Aplicaciones de WPF
En las aplicaciones de WPF, utilice uno de los métodos Dispatcher.Invoke para ejecutar un delegado en el subproceso de la interfaz de usuario. Si es necesario, utilice el método Dispatcher.CheckAccess para determinar si un método se está ejecutando en un subproceso ajeno a la interfaz de usuario.
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);
}
}
Aplicaciones de Windows Forms
En las aplicaciones de Windows Forms, use el método Control.Invoke para ejecutar un delegado que actualice el subproceso de la interfaz de usuario. Si es necesario, utilice la propiedad Control.InvokeRequired para determinar si un método se está ejecutando en un subproceso ajeno a la interfaz de usuario.
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();
}
}
Aplicaciones de la Tienda
En las aplicaciones de la Tienda, use el método CoreDispatcher.RunAsync para ejecutar un delegado que actualice el subproceso de la interfaz de usuario. Si es necesario, utilice la propiedad HasThreadAccess para determinar si un método se está ejecutando en un subproceso ajeno a la interfaz de usuario.
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;});
}
}
En este artículoEn esta sección
Una declaración de un bloque de foreach (For Each en Visual Basic) cambia la colección que está iterando
Provocación de una excepción InvalidOperationException con foreach | Evitar excepciones InvalidOperationException en bucles
Las instrucciones foreach (For Each en Visual Basic) repiten un grupo de instrucciones incrustadas por cada elemento de una matriz o una colección que implementa la interfaz IEnumerable o IEnumerable. La instrucción foreach se utiliza para procesar una iteración en la colección para leer y modificar los elementos, pero no se puede usar para agregar o quitar elementos de la colección. Si agrega o quita elementos de la colección de origen de una instrucción foreach, se lanza una InvalidOperationException.
Provocación de una excepción InvalidOperationException con foreach
En este ejemplo, se lanza una InvalidOperationException cuando la instrucción numList.Add(5); trata de modificar la lista del bloque foreach.
Mensaje de excepción:
- Información adicional: colección modificada; puede que no se ejecute la operación de enumeración.
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);
}
}
En este artículoEn esta sección
Evitar excepciones InvalidOperationException en bucles
Importante
Agregar elementos a una lista o quitarlos de una lista mientras está iterando por la colección puede tener efectos secundarios no deseados y difíciles de predecir.Si es posible, debe mover la operación fuera de la iteración.
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);
}
}
Si la situación requiere que agregue elementos a una lista o que quite elementos de una lista al iterar una colección, utilice un bucle for (For en 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);
}
}
En este artículoEn esta sección
Un Nullable<T> que es NULL se convierte en T
Provocación de una excepción InvalidOperationException con una conversión no válida | Evitar una excepción InvalidOperationException por una conversión incorrecta
Si convierte una estructura Nullable que es null (Nothing en Visual Basic) en su tipo subyacente, se lanzará una excepción InvalidOperationException.
Provocación de una excepción InvalidOperationException con una conversión no válida
En este método, cuando el método Select convierte un elemento NULL de dbQueryResults en un int, se lanza una InvalidOperationException.
Mensaje de excepción:
- Información adicional: e objeto que acepta valores Null debe tener un valor.
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();
}
En este artículoEn esta sección
Evitar una excepción InvalidOperationException por una conversión incorrecta
Para evitar InvalidOperationException:
Utilice la propiedad Nullable.HasValue para seleccionar solamente los elementos que no son NULL.
Utilice uno de los métodos Nullable.GetValueOrDefault sobrecargados para proporcionar un valor predeterminado a un elemento NULL.
Ejemplo de 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());
}
Ejemplo de Nullable<T>.GetValueOrDefault
En este ejemplo, utilizamos GetValueOrDefault(UTP) para especificar un valor predeterminado que está fuera de los valores esperados devueltos por 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();
}
En este artículoEn esta sección
Se llama a un método System.Linq.Enumerable en una colección vacía
Los métodos Enumerable Aggregate, Average, Last, Max, Min, First, Single y SingleOrDefault realizan operaciones en una secuencia y devuelven un único resultado.
Algunas sobrecargas de estos métodos lanzan una excepción InvalidOperationException cuando la secuencia está vacía (otras sobrecargas devuelven null (Nothing en Visual Basic). SingleOrDefault también lanza InvalidOperationException cuando la secuencia contiene más de un elemento.
Sugerencia
La mayoría de los métodos Enumerable de los que se habla en esta sección están sobrecargados.Asegúrese de que comprende el comportamiento de la sobrecarga que elija.
Mensajes de excepción:
Métodos Aggregate, Average, Last, Max y Min | Métodos First y FirstOrDefault | Métodos Single y SingleOrDefault
Métodos Aggregate, Average, Last, Max y Min
- Información adicional: la secuencia no contiene elementos
Provocación de una excepción InvalidOperationException con Average
En este ejemplo, el siguiente método lanza una InvalidOperationException cuando llama al método Average porque la expresión Linq devuelve una secuencia que no contiene elementos mayores que 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);
}
Cuando utiliza uno de estos métodos sin comprobar si hay una secuencia vacía, está asumiendo de forma implícita que la secuencia no está vacía y que una secuencia vacía es un suceso inesperado. En los casos en los que, efectivamente, presuponga que la secuencia no estará vacía, será adecuado capturar o lanzar la excepción.
Evitar una excepción InvalidOperationException causada por una secuencia vacía
Utilice uno de los métodos Enumerable.Any para comprobar si la secuencia está vacía.
Sugerencia
Usar Any puede mejorar el rendimiento de la comprobación si es posible que la secuencia para obtener el promedio contenga un gran número de elementos, o si la operación que genera la secuencia es costosa.
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.");
}
}
En este artículoEn esta sección
Métodos First y FirstOrDefault
First devuelve el primer elemento de una secuencia o lanza una InvalidOperationException si la secuencia está vacía. Puede llamar al método FirstOrDefault en lugar de a First para devolver un valor especificado o predeterminado en vez de lanzar la excepción.
Nota
En .NET Framework, los tipos tienen un concepto de los valores predeterminados.Por ejemplo, para un tipo de referencia cualquiera, el valor predeterminado es NULL, y para un tipo entero, es cero.Vea Tabla de valores predeterminados (Referencia de C#)
Provocación de una excepción InvalidOperationException con First
First es un método de optimización que devuelve el primer elemento de una secuencia que cumple una condición especificada. Si no se encuentra ningún elemento que cumpla la condición, los métodos lanzan una excepción InvalidOperationException.
En este ejemplo, el método First lanza la excepción porque dbQueryResults no contiene ningún elemento mayor que 4.
Mensaje de excepción:
- Información adicional: la secuencia no contiene ningún elemento coincidente
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);
}
Evitar una excepción con FirstOrDefault
Puede utilizar uno de los métodos FirstOrDefault en lugar de First para comprobar que la secuencia firstNum contiene al menos un elemento. Si la consulta no encuentra ningún elemento que cumpla la condición, devuelve un valor especificado o predeterminado. Puede comprobar el valor devuelto para determinar si se encontró algún elemento.
Nota
Si el valor predeterminado del tipo es un elemento válido en la secuencia, puede ser más complicado implementar el uso de 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.");
}
}
En este artículoEn esta sección
Métodos Single y SingleOrDefault
Los métodos Enumerable.Single devuelven el único elemento de una secuencia, o el único elemento de una secuencia que supera una prueba especificada.
Si no hay ningún elemento en la secuencia, o si hay más de un elemento en la secuencia, el método lanza una excepción InvalidOperationException.
Puede usar SingleOrDefault para devolver un valor especificado o predeterminado en lugar de lanzar la excepción cuando la secuencia no contiene ningún elemento. Aun así, SingleOrDefault lanza una InvalidOperationException cuando la secuencia contiene más de un elemento que coincide con el predicado de selección.
Nota
En .NET Framework, los tipos tienen un concepto de los valores predeterminados.Por ejemplo, para un tipo de referencia cualquiera, el valor predeterminado es NULL, y para un tipo entero, es cero.Vea Tabla de valores predeterminados (Referencia de C#)
Provocación de excepciones InvalidOperationException con Single
En este ejemplo, singleObject lanza una InvalidOperationException porque dbQueryResults no contiene ningún elemento mayor que 4.
Mensaje de excepción:
- Información adicional: la secuencia no contiene ningún elemento coincidente
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);
}
Provocación de excepciones InvalidOperationException con Single o SingleOrDefault
En este ejemplo, singleObject lanza una InvalidOperationException porque dbQueryResults contiene más de un elemento mayor que 2.
Mensaje de excepción:
- Información adicional: la secuencia contiene más de un elemento coincidente
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.");
}
}
Evitar excepciones InvalidOperationException con Single
Usar Single y SingleOrDefault también sirve como documentación de sus intenciones. Single implica claramente que espera que la condición devuelva un solo resultado y no más. SingleOrDefault declara que espera uno o cero resultados, pero no más. Cuando estas condiciones no son válidas, es adecuado lanzar o capturar la InvalidOperationException. Sin embargo, si es de esperar que las condiciones no válidas se produzcan con cierta frecuencia, debe plantearse usar otros métodos Enumerable, como First o Where, para generar los resultados.
Durante el desarrollo, puede usar uno de los métodos Assert para comprobar sus suposiciones. En este ejemplo, el código resaltado hace que el depurador se interrumpa y muestra un cuadro de diálogo de aserción durante el desarrollo. La aserción se quita en el código de la versión de lanzamiento y, si los resultados no son válidos, se lanzará cualquier Single.
Nota
Si usa Take``1 y establece su parámetro count en 2, limitará la secuencia devuelta a dos elementos como máximo.Esta secuencia incluye todos los casos que tiene que comprobar (0, 1 y más de 1 elementos) y puede mejorar el rendimiento de la comprobación en los casos en los que es posible que la secuencia contenga un gran número de elementos, o si la operación que genera la secuencia es costosa.
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());
}
Si quiere evitar la excepción y, al mismo tiempo, controlar los estados no válidos en el código de la versión de lanzamiento, puede modificar la técnica que describimos arriba. En este ejemplo, el método responde al número de elementos devueltos por moreThan2 en la instrucción 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;
}
}
En este artículoEn esta sección
Artículos relacionados
Instrucciones de diseño de excepciones (Instrucciones de diseño de .NET Framework)
Controlar y generar excepciones (Elementos esenciales de aplicaciones de .NET Framework)
Cómo: Controlar excepciones en una consulta PLINQ (Guía de desarrollo para .NET Framework)
Excepciones en subprocesos administrados (Guía de desarrollo para .NET Framework)
Excepciones y control de excepciones (Guía de programación de C#)
Instrucciones para el control de excepciones (Referencia de C#)
Instrucción Try...Catch...Finally (Visual Basic)
Control de excepciones (Task Parallel Library)
Control de excepciones (Depurar)
Tutorial: Controlar una excepción de simultaneidad (Acceder a los datos en Visual Studio)
Cómo: Controlar errores y excepciones que se producen con el enlace de datos (Windows Forms)
Controlar excepciones en aplicaciones de red (XAML) (Windows)
En este artículo