C# is vs as: Type Checking and Safe Casting
c# is vs as: Understand the differences between C#'s is and as operators for type checking and safe casting, including syntax, runtime behavior, and performance.
When you need to check whether an object is of a certain type or convert it to that type, C# offers two operators: is and as. Both are used for type testing and conversion, but they behave differently and are suited to different scenarios. Understanding c# is vs as is essential for writing type-safe code that handles runtime types correctly.
The is Operator for Type Checking
The is operator evaluates a type pattern and returns a boolean indicating whether the runtime type of an expression is compatible with a given type. The simplest form is:
object value = "hello"; if (value is string) { Console.WriteLine("value is a string"); }
Here, value is string returns true if the runtime type of value is string or a type derived from string (since string is sealed, that means exactly string). For reference types, is performs a type compatibility check similar to the typeof check but without throwing exceptions. It returns false if the expression is null or if the type does not match.
Since C# 7.0, is supports pattern matching, allowing you to declare a variable in the same expression:
if (value is string text) { Console.WriteLine(text.Length); }
This combines a type test with a cast, assigning text only when the type matches. The variable text is in scope inside the if block and is definitely assigned. This pattern is more concise than a separate cast and null check.
The as Operator for Safe Casting
The as operator performs a safe cast that returns null instead of throwing an InvalidCastException when the conversion fails. It works only with reference types and nullable value types. For example:
object value = "hello"; string text = value as string; if (text != null) { Console.WriteLine(text.Length); }
If value is not a string or is null, text becomes null. The as operator does not work with non-nullable value types like int or double. Attempting to use as with a value type results in a compile-time error: Cannot use 'as' operator with type 'int' because it is a value type. To cast value types, you must use a regular cast or is with a pattern.
Key Differences Between is and as
| Operator | Returns | Throws on failure | Works with value types | Typical use |
|---|---|---|---|---|
is | bool | No | Yes (with pattern) | Type checking and pattern matching |
as | The converted type or null | No | No (only reference and nullable value types) | Safe casting to a reference type |
The most important difference is the return type. is gives you a boolean, so you can use it in conditionals without worrying about the actual converted value. as gives you the converted value or null, so you must check for null before using it. This makes as a direct replacement for the old pattern:
if (value is SomeType) { SomeType st = (SomeType)value; // ... }
which can be rewritten as:
SomeType st = value as SomeType; if (st != null) { // ... }
The is pattern approach is often more readable because it declares the variable inline.
Performance and Runtime Behavior
Both is and as are implemented as IL instructions (isinst for as, and isinst plus a branch for is in its simple form). In practice, the runtime cost is similar for simple type checks. However, pattern matching with is can introduce additional overhead if the pattern is complex, such as property patterns or recursive patterns. For simple type checks, the difference is negligible.
One subtle performance consideration is that using as followed by a null check avoids a double type check. With the old is + cast pattern, you perform two type checks: one in is and one in the cast. With as, you perform only one. The modern is pattern also performs only one check because it combines the test and the assignment.
For value types, is with a type pattern avoids boxing in some cases. For example:
object boxed = 42; if (boxed is int number) { // number is an int, no unboxing needed }
The pattern extracts the value directly. Using as is not possible here because int is a value type.
Common Mistakes and How to Avoid Them
A frequent mistake is using as with a value type. The compiler rejects this, but developers sometimes try to work around it by boxing first:
object boxed = 42; int? maybeInt = boxed as int?; // This works because int? is a nullable value type
as works with Nullable<T> because it is a reference type at the runtime level. However, this is often less clear than using is:
if (boxed is int number) { // use number }
Another mistake is using is to check for null. While value is null is valid and equivalent to value == null, it is less idiomatic. The is operator is designed for type checks, not null checks. Use == or the is pattern with a type.
Also, remember that as returns null for a failed cast, so if the original object is null, the result is also null. This can be useful, but it means you cannot distinguish between "the object was null" and "the object was of a different type" without additional checks.
Choosing Between is and as
Use is when you need to:
- Check if an object is of a specific type without needing the converted value.
- Use pattern matching to extract the value in a single step.
- Work with value types.
Use as when you need to:
- Convert a reference type to a derived type and are comfortable handling
nullfor failure. - Prefer a separate null check after the cast.
- Need to maintain compatibility with older C# versions (pre-7.0) where
isdid not support patterns.
In modern C#, the is pattern is often the better choice because it combines the type test and the conversion, leading to clearer and more concise code. However, if you need to reuse the converted value in multiple places, as might be more convenient because you assign it once and check for null once.
Pattern Matching with is in Modern C#
C# 7.0 introduced type patterns, and later versions added property patterns, positional patterns, and relational patterns. These make is much more powerful than a simple type check. For example:
public static string Describe(object obj) { if (obj is Point p && p.X > 0 && p.Y > 0) { return "Point in first quadrant"; } else if (obj is string s && s.Length > 10) { return "Long string"; } return "Unknown"; }
Here, is is used not only for type checking but also for evaluating conditions on the extracted value. This capability goes far beyond what as can do. When you need complex type-based logic, is pattern matching is the idiomatic choice.
The as operator remains useful in scenarios where you want a simple, null-based conversion without pattern matching. For example, in a method that accepts object and returns a string representation, you might use:
string text = value as string; if (text != null) { return text.ToUpper(); }
This is straightforward and works in all C# versions.
In summary, c# is vs as is not about which operator is "better" in absolute terms; it is about selecting the right tool for the specific type-checking and conversion scenario. The is operator with pattern matching offers more expressive power, while as provides a simple, null-based cast for reference types. Understanding their differences helps you write code that is both correct and maintainable.