Skip to main content

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.

ℹ️ What You'll Learn
  • is operator - type checking and pattern matching
  • as operator - safe casting without exceptions
  • typeof - get type at compile-time
  • nameof - refactor-safe string names
  • sizeof - 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

OperatorReturnsUse WhenExampleThrows
is checkboolYou need type checkobj is StudentNo
is patternbool + variableCheck AND declare variableobj is Student sNo
is notboolNegate type checkobj is not nullNo
asobject or nullSafe cast, nullable resultobj as StudentNo
hard castobjectCertain of type(Student)objYes
typeofTypeGet type at compile-timetypeof(Student)No
GetType()TypeGet actual type at runtimeobj.GetType()No
nameofstringGet name as string, refactor-safenameof(Student)No
sizeofintGet byte size of structsizeof(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

💻 Try It — Console App
💡 Paste into Program.cs and press F5⌥ GitHub
public class SchoolMemberProcessor
{
public void Process(List<Person> members)
{
foreach (var member in members)
{
// is + pattern variable - clean type check
if (member is Student { Percentage: < 35 } failed)
{
Console.WriteLine($"FAIL: {failed.Name} - {failed.Percentage:F1}%");
continue;
}

if (member is Teacher teacher)
{
Console.WriteLine($"TEACHER: {teacher.Name} | {teacher.Subject}");
continue;
}

if (member is Student student)
Console.WriteLine($"PASS: {student.Name} | {student.GetGrade()}");
}
}

public string GetMemberType(object obj)
{
// typeof for type name display
string typeName = obj?.GetType()?.Name ?? "Unknown";

// as for safe cast
if (obj as Student != null) return $"Student ({typeName})";
if (obj as Teacher != null) return $"Teacher ({typeName})";
return typeName;
}
}

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

  1. Use is pattern for clean type checks - Cleaner than as + null check
  2. Use as for safe casting without exceptions - When failure is expected
  3. Use hard cast only when certain - When type is guaranteed
  4. Use typeof for compile-time type info - Type parameters, reflection setup
  5. Use GetType() for runtime type checking - Actual object type
  6. Use nameof instead of string literals - Refactor-safe, IDE support
  7. Combine patterns with switch expressions - Readable polymorphic dispatch
  8. Avoid redundant type checks - Use pattern matching once, use variable
  9. Document pattern matching intent - Complex patterns need explanation
  10. Test edge cases - null, inheritance hierarchies, empty collections
  11. Use property patterns for validation - is Student { Percentage: >= 90 }
  12. Keep type operators readable - Complex nesting becomes confusing

🎯 Q1: What's the difference between is, as, and hard casting?

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.

🎯 Q2: Why use nameof instead of string literals?

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.

🎯 Q3: What's the difference between typeof and GetType()?

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.

🎯 Q4: Show me pattern matching with property patterns?

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.

🎯 Q5: When should I use hard cast vs as?

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.

🎯 Q6: How do I safely check type in polymorphic collections?

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();

ℹ️ Video Tutorial

Type operators explained: is, as, typeof, nameof, pattern matching, null checks. Video coming soon. Subscribe to NexCoding YouTube for updates.


🤖Use AI to Learn Faster

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.

Next Article

Object Methods ->

nexcoding.in