System.InvalidCastException: Unable to cast object of type 'X' to type 'Y'.
ID: dotnet/invalid-cast-exception
Version Compatibility
| Version | Status | Introduced | Deprecated | Notes |
|---|---|---|---|---|
| 8 | active | — | — | — |
Root Cause
InvalidCastException occurs when an explicit cast or unboxing operation fails at runtime because the actual object type is not compatible with the target type. Common scenarios include: casting objects from untyped collections (ArrayList, DataRow fields), incorrect unboxing of value types (e.g., casting an int boxed as object to long), COM interop type mismatches, and interface cast failures when the same assembly is loaded from different paths. On .NET 8 Linux, a particularly common cause is assembly loading contexts where the 'same' type loaded from different AssemblyLoadContexts are treated as distinct types. Fix by using safe casting patterns (as/is), proper generic collections, and ensuring consistent assembly loading.
genericWorkarounds
-
92% success Use pattern matching or the 'as' operator with null checks instead of direct casts
Replace direct casts with safe patterns: instead of var result = (MyType)obj; use if (obj is MyType result) { /* use result */ } else { /* handle mismatch, log obj.GetType().AssemblyQualifiedName */ }. For value type unboxing, ensure the exact type matches: an int boxed as object must be unboxed to int first, then converted: var value = (long)(int)obj; not (long)obj. Use pattern matching with type checks for polymorphic scenarios -
90% success Replace untyped collections with generic equivalents to get compile-time type safety
Replace ArrayList with List<T>, Hashtable with Dictionary<TKey, TValue>, and DataRow field access with strongly-typed models. For DataRow: instead of (decimal)row["Price"], use row.Field<decimal>("Price") which handles DBNull and performs proper type conversion. For legacy APIs returning object, create typed wrapper methods: public static T GetValue<T>(DataRow row, string column) => row.IsNull(column) ? default! : row.Field<T>(column); -
85% success Fix assembly loading context issues by ensuring a single copy of shared types
When the error shows the same type name for source and target (e.g., 'Unable to cast MyApp.Models.User to MyApp.Models.User'), the type is loaded from different assembly contexts. Diagnose with: Console.WriteLine(obj.GetType().Assembly.Location); Console.WriteLine(typeof(MyType).Assembly.Location); Fix by ensuring plugins or dynamically loaded assemblies share the host's types via AssemblyLoadContext.Default or by using a shared contract assembly. In plugin scenarios, configure the AssemblyDependencyResolver to resolve shared types from the host context
Dead Ends
Common approaches that don't work:
-
Suppressing the exception by wrapping every cast in try-catch and returning a default value
90% fail
This masks the real type mismatch and leads to silent data corruption or logic errors downstream. The cast failure indicates a fundamental type contract violation that needs to be understood and fixed, not hidden. Default values propagating through the system create bugs that are much harder to diagnose later
-
Using Convert.ChangeType() as a universal replacement for direct casts
75% fail
Convert.ChangeType() only works for types that implement IConvertible (primitive types, string, DateTime). It will throw InvalidCastException or FormatException for custom types, complex objects, and many framework types. It also does not handle nullable types correctly, throwing for Nullable<T> targets
-
Assuming the cast fails because of an inheritance issue and adding a base class or interface
85% fail
When the types look identical but the cast fails (common error message: 'Unable to cast Type to Type' with same names), the issue is usually assembly loading: the same type loaded from two different assembly contexts are distinct CLR types. Adding inheritance relationships does not help because the runtime sees them as completely unrelated types from different assemblies