40. Type Operators in C#
Level: Intermediate
Goal: Check object types safely and cast without crashes.
Scenarios:
Person person = GetPerson(); // Could be Student, Teacher, or Staff
if (person is Student) // Check type safely
{
Student s = person as Student; // Cast safely
}
Type operators prevent crashes from wrong casts.
isoperator - type checking and pattern matchingasoperator - safe casting without exceptionstypeof- get type at compile-timenameof- refactor-safe string namessizeof- memory size of value types- Hard cast vs
as- when to use each - Pattern matching - property patterns, switch expressions
- Type checking best practices
Type Operators Reference
| Operator | Returns | Use When | Example | Throws |
|---|---|---|---|---|
| is check | bool | You need type check | obj is Student | No |
| is pattern | bool + variable | Check AND declare variable | obj is Student s | No |
| is not | bool | Negate type check | obj is not null | No |
| as | object or null | Safe cast, nullable result | obj as Student | No |
| hard cast | object | Certain of type | (Student)obj | Yes |
| typeof | Type | Get type at compile-time | typeof(Student) | No |
| GetType() | Type | Get actual type at runtime | obj.GetType() | No |
| nameof | string | Get name as string, refactor-safe | nameof(Student) | No |
| sizeof | int | Get byte size of struct | sizeof(int) | No |
is - Type Check + Pattern Match
object member = new Student { Name = "Sahasra", Percentage = 87.5 };
// Basic type check
if (member is Student)
Console.WriteLine("Is a Student");
// Type pattern - check AND declare variable
if (member is Student student)
Console.WriteLine($"Student: {student.Name}"); // student available here
// Property pattern
if (member is Student { Percentage: >= 90 })
Console.WriteLine("Top student");
// Combined
if (member is Student s && s.Percentage >= 75)
Console.WriteLine($"{s.Name} - Distinction");
// Negation
if (member is not null)
Console.WriteLine("Not null");
// Switch with is patterns
static string Describe(object obj)
{
switch (obj)
{
case Student { Percentage: >= 90 }:
return "Topper";
case Student s:
return $"Student: {s.Percentage:F1}%";
case Teacher t:
return $"Teacher: {t.Subject}";
case null:
return "No member";
default:
return "Unknown";
}
}
as - Safe Cast (Returns null)
object member = new Student { Name = "Sahasra" };
// as - returns null if cast fails (no exception)
Student? student = member as Student;
if (student != null)
Console.WriteLine(student.Name);
// Hard cast - throws InvalidCastException if fails
Student s2 = (Student)member; // throws if not Student
// as vs hard cast
object obj = "Hello";
Student? s3 = obj as Student; // null - no exception
// Student s4 = (Student)obj; // [X] throws InvalidCastException
// as only works with reference types and nullable value types
// int? n = obj as int; <- needs nullable
int? n = obj as int?; // null if not int
is vs as - When to Use
// is - when you need to check AND use the value
if (member is Student s && s.Percentage >= 75)
Console.WriteLine(s.Name);
// as - when you want null on failure
Student? s2 = member as Student;
if (s2 != null) { /* use s2 */ }
// Hard cast - when you are CERTAIN of type (throws if wrong)
Student s3 = (Student)GetVerifiedStudent();
typeof - Get Type at Compile Time
// typeof - known at compile time, no instance needed
Type studentType = typeof(Student);
Type intType = typeof(int);
Type listType = typeof(List<Student>);
Console.WriteLine(studentType.Name); // "Student"
Console.WriteLine(studentType.FullName); // "SchoolManagement.Models.Student"
Console.WriteLine(intType.IsValueType); // True
Console.WriteLine(studentType.IsClass); // True
// Practical use - reflection
foreach (var prop in typeof(Student).GetProperties())
Console.WriteLine($"{prop.Name}: {prop.PropertyType.Name}");
// Generic type info
Console.WriteLine(typeof(List<Student>).GetGenericArguments()[0].Name); // "Student"
// typeof vs GetType()
// typeof - compile time, works without instance
// GetType() - runtime, needs actual instance
Student s = new Student();
Console.WriteLine(s.GetType() == typeof(Student)); // True
Console.WriteLine(s.GetType().Name); // "Student"
nameof - Refactor-Safe String
// nameof - gets name of variable/property/method as string
// Updated automatically if renamed - unlike hard-coded strings
public class Student
{
private double _percentage;
public double Percentage
{
get => _percentage;
set
{
if (value < 0 || value > 100)
throw new ArgumentOutOfRangeException(
nameof(value), // "value" - updates if renamed
"Must be 0-100.");
_percentage = value;
}
}
public void Enroll(string name, string className)
{
// nameof captures parameter name
ArgumentNullException.ThrowIfNull(name, nameof(name));
ArgumentNullException.ThrowIfNull(className, nameof(className));
}
}
// Logging - safer than string literals
Console.WriteLine(nameof(Student)); // "Student"
Console.WriteLine(nameof(Student.Percentage)); // "Percentage"
// INotifyPropertyChanged pattern
private string _name = "";
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name)); // not "Name" as magic string
}
}
sizeof - Value Type Size in Bytes
// sizeof - size of value type in bytes
// Works without unsafe for primitive types in C# 11+
Console.WriteLine(sizeof(int)); // 4
Console.WriteLine(sizeof(double)); // 8
Console.WriteLine(sizeof(char)); // 2
Console.WriteLine(sizeof(bool)); // 1
Console.WriteLine(sizeof(decimal)); // 16
Console.WriteLine(sizeof(long)); // 8
// For structs - needs unsafe (or use Unsafe.SizeOf<T>())
// .NET 5+ alternative - no unsafe needed:
Console.WriteLine(System.Runtime.CompilerServices.Unsafe.SizeOf<DateTime>()); // 8
School Management - Practical Usage
Common Mistakes
? Using hard cast instead of as (throws on mismatch):
object member = "Hello";
// Hard cast - throws if not Student
Student student = (Student)member; // ? InvalidCastException
// Better - use as for safe conversion
Student? student2 = member as Student; // ? null if not Student
if (student2 != null)
Console.WriteLine(student2.Name);
? Use as when unsure of type:
public void ProcessMember(object obj)
{
Student? s = obj as Student;
if (s != null) { /* use s */ }
}
? Using typeof to check actual object type (use GetType()):
var student = new Student();
// typeof is compile-time, doesn't work with variables
Type t = typeof(Student); // OK
// Type actual = typeof(student); // Error
// GetType() is runtime
Type actual = student.GetType(); // ? Student type
? Use GetType() for runtime type checking:
if (obj.GetType() == typeof(Student))
Console.WriteLine("Is exactly Student");
if (obj.GetType().IsAssignableTo(typeof(Person)))
Console.WriteLine("Is Student or subclass");
? Using magic strings instead of nameof (breaks on rename):
public double Percentage
{
set
{
if (value < 0)
throw new ArgumentException("Percentage"); // String literal
}
}
// If you rename property, this string doesn't update
? Use nameof for refactor-safe strings:
public double Percentage
{
set
{
if (value < 0)
throw new ArgumentException(nameof(Percentage)); // Updates on rename
}
}
// If you rename Percentage, nameof updates automatically
? Assuming as works with non-nullable value types:
object age = 25;
// ? as doesn't work with int
int i = age as int; // ERROR
// ? Must use nullable
int? i2 = age as int?; // null if not int
if (i2.HasValue) { }
? Use nullable for value type casting:
int? result = age as int?;
if (result != null)
Console.WriteLine($"Age: {result}");
? Not checking null before using is pattern:
object member = null;
if (member is Student s) // s might be null if member is Student?
Console.WriteLine(s.Name);
? Combine is with null check:
if (member is Student s && s != null)
Console.WriteLine(s.Name);
// or use property pattern
if (member is Student { Name: not null })
Console.WriteLine(member.Name);
Best Practices
- Use
ispattern for clean type checks - Cleaner thanas+ null check - Use
asfor safe casting without exceptions - When failure is expected - Use hard cast only when certain - When type is guaranteed
- Use
typeoffor compile-time type info - Type parameters, reflection setup - Use
GetType()for runtime type checking - Actual object type - Use
nameofinstead of string literals - Refactor-safe, IDE support - Combine patterns with switch expressions - Readable polymorphic dispatch
- Avoid redundant type checks - Use pattern matching once, use variable
- Document pattern matching intent - Complex patterns need explanation
- Test edge cases - null, inheritance hierarchies, empty collections
- Use property patterns for validation -
is Student { Percentage: >= 90 } - Keep type operators readable - Complex nesting becomes confusing
is: Type check returns bool. With pattern, declares variable of checked type.
as: Safe cast, returns null if fails (no exception).
Hard cast (): Throws exception if type mismatch. Use only when certain.
object obj = new Student();
// is - check + declare
if (obj is Student s)
Console.WriteLine(s.Name); // s is Student variable
// as - safe cast, null on fail
Student? s2 = obj as Student;
if (s2 != null) { }
// Hard cast - throws on fail
Student s3 = (Student)obj; // Exception if not Student
Rule: Use is for checking, as for safe casting, hard cast sparingly.
nameof is refactor-safe. If you rename property, nameof updates automatically. String literals don't.
public string Name { get; set; }
// String literal - breaks if Name renamed
throw new ArgumentException("Name is required"); // Magic string
// nameof - updates automatically
throw new ArgumentException(nameof(Name)); // Refactor-safe
When Name ? FullName, nameof(Name) still shows "Name" but compiler catches it. Magic string stays wrong.
Always use nameof for property/parameter names.
typeof: Compile-time, no instance needed. Static type.
GetType(): Runtime, needs instance. Actual type.
Person p = new Student();
typeof(Person) // Person (static, compile-time)
p.GetType() // Student (actual, runtime)
Use typeof for type metadata. Use GetType() for runtime type checking.
// typeof - reflection, setting up handlers
var methods = typeof(Student).GetMethods();
// GetType() - runtime dispatch
if (p.GetType() == typeof(Student))
Console.WriteLine("Actual type is Student");
Rule: typeof for types you know at compile-time, GetType() for runtime checks.
Property patterns check type AND properties in one expression.
public record Student(string Name, double Percentage);
var members = new object[]
{
new Student("Sahasra", 95),
new Student("Priya", 85),
new Student("Arjun", 30)
};
foreach (var member in members)
{
// Property pattern - type + property check
if (member is Student { Percentage: >= 90 })
Console.WriteLine("Topper");
else if (member is Student { Percentage: >= 35 })
Console.WriteLine("Pass");
else if (member is Student { Percentage: < 35 })
Console.WriteLine("Fail");
}
Switch expression with patterns:
string GetGrade(object obj) => obj switch
{
Student { Percentage: >= 90 } => "A+",
Student { Percentage: >= 80 } => "A",
Student { Percentage: >= 35 } => "B",
Student => "F",
null => "No data",
_ => "Unknown"
};
Pattern matching = cleaner, more readable type dispatch.
as: Unsure of type, failure is normal.
Hard cast: Certain of type, failure is a bug.
// Defensive code - might not be Student
public void Process(object obj)
{
Student? s = obj as Student;
if (s != null) { /* use s */ } // Safe
}
// Verified code - guaranteed Student
public void ProcessStudent(object obj)
{
Student s = (Student)obj; // Throw if wrong - indicates bug
Console.WriteLine(s.Name);
}
as = defensive. Hard cast = assertive. Choose based on code confidence.
Use is pattern with switch expression for clean dispatch.
var members = new List<Person>
{
new Student { Name = "Sahasra", Percentage = 87.5 },
new Teacher { Name = "Dr. Mehta", Subject = "Maths" }
};
foreach (var member in members)
{
string info = member switch
{
Student { Percentage: >= 90 } s => $"Topper: {s.Name}",
Student s => $"Student: {s.Name}",
Teacher t => $"Teacher: {t.Name}",
null => "Unknown",
_ => "Other"
};
Console.WriteLine(info);
}
Switch with patterns = readable, exhaustive, type-safe polymorphism.
When You'll Use This in SMS
SMS uses type operators constantly:
if (obj is Student student)
Console.WriteLine($"Name: {student.Name}");
string className = typeof(Student).Name;
var repo = obj as IRepository;
if (repo != null)
repo.Save();
Type operators explained: is, as, typeof, nameof, pattern matching, null checks. Video coming soon. Subscribe to NexCoding YouTube for updates.
Use ChatGPT, Claude, or Copilot to go deeper on C# is, as, typeof, nameof operators. Try these prompts:
"What is the difference between is, as, and hard casting in C#?""When should I use nameof instead of a string literal in C#?""What is the difference between typeof and GetType() in C#?""Show me a pattern matching example using is with property patterns"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.