C# Type Information
Introduction
In C#, reflection provides powerful capabilities to examine the metadata of types at runtime. Understanding how to access and use type information is a fundamental aspect of reflection that enables developers to create dynamic, flexible, and extensible applications.
Type information allows you to:
- Discover the properties, methods, and fields of a class at runtime
- Determine inheritance relationships between types
- Check for implemented interfaces
- Examine attributes applied to types and members
- Create instances dynamically based on type information
This guide will walk you through how to access and work with type information in C# using reflection.
Getting Type Information
The Type
Class
The Type
class is the primary entry point for all reflection operations. It encapsulates metadata about a type and provides methods for examining this metadata.
There are several ways to get a Type
object:
Using the typeof
Operator
Type stringType = typeof(string);
Type intType = typeof(int);
Type listType = typeof(List<string>);
Console.WriteLine($"Type name: {stringType.Name}");
Console.WriteLine($"Full type name: {stringType.FullName}");
Console.WriteLine($"Is primitive: {intType.IsPrimitive}");
Console.WriteLine($"Is generic: {listType.IsGenericType}");
Output:
Type name: String
Full type name: System.String
Is primitive: True
Is generic: True
Using GetType()
Method
string text = "Hello, Reflection!";
int number = 42;
Type stringInstanceType = text.GetType();
Type intInstanceType = number.GetType();
Console.WriteLine($"Type: {stringInstanceType.Name}");
Console.WriteLine($"Type equals typeof(string): {stringInstanceType == typeof(string)}");
Output:
Type: String
Type equals typeof(string): True
Using Type.GetType()
Method
// Get type using fully qualified name
Type stringTypeFromName = Type.GetType("System.String");
Type listTypeFromName = Type.GetType("System.Collections.Generic.List`1[[System.Int32, System.Private.CoreLib]]");
Console.WriteLine($"Type name: {stringTypeFromName?.Name ?? "Not found"}");
Console.WriteLine($"List type found: {listTypeFromName != null}");
Output:
Type name: String
List type found: True
Examining Type Details
Basic Type Properties
Once you have a Type
object, you can examine various properties:
Type type = typeof(DateTime);
Console.WriteLine($"Type name: {type.Name}");
Console.WriteLine($"Full name: {type.FullName}");
Console.WriteLine($"Namespace: {type.Namespace}");
Console.WriteLine($"Assembly: {type.Assembly.GetName().Name}");
Console.WriteLine($"Is class: {type.IsClass}");
Console.WriteLine($"Is value type: {type.IsValueType}");
Console.WriteLine($"Is sealed: {type.IsSealed}");
Console.WriteLine($"Is abstract: {type.IsAbstract}");
Console.WriteLine($"Is interface: {type.IsInterface}");
Console.WriteLine($"Is enum: {type.IsEnum}");
Output:
Type name: DateTime
Full name: System.DateTime
Namespace: System
Assembly: System.Private.CoreLib
Is class: False
Is value type: True
Is sealed: True
Is abstract: False
Is interface: False
Is enum: False
Type Hierarchy and Relationships
Reflection allows you to examine the inheritance hierarchy and relationships between types:
public class Animal { }
public class Dog : Animal, IComparable<Dog> { }
Type dogType = typeof(Dog);
Type baseType = dogType.BaseType;
Type[] interfaces = dogType.GetInterfaces();
Console.WriteLine($"Type: {dogType.Name}");
Console.WriteLine($"Base type: {baseType.Name}");
Console.WriteLine("Implemented interfaces:");
foreach (var interface1 in interfaces)
{
Console.WriteLine($"- {interface1.Name}");
}
Output:
Type: Dog
Base type: Animal
Implemented interfaces:
- IComparable`1
Accessing Type Members
Getting Properties
Type personType = typeof(Person);
PropertyInfo[] properties = personType.GetProperties();
Console.WriteLine("Properties of Person class:");
foreach (var property in properties)
{
Console.WriteLine($"- {property.Name} (Type: {property.PropertyType.Name})");
}
// Sample Person class
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime DateOfBirth { get; set; }
}
Output:
Properties of Person class:
- Name (Type: String)
- Age (Type: Int32)
- DateOfBirth (Type: DateTime)
Getting Methods
Type calculatorType = typeof(Calculator);
MethodInfo[] methods = calculatorType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
Console.WriteLine("Methods of Calculator class:");
foreach (var method in methods)
{
string parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($"- {method.ReturnType.Name} {method.Name}({parameters})");
}
// Sample Calculator class
public class Calculator
{
public int Add(int a, int b) => a + b;
public int Subtract(int a, int b) => a - b;
public int Multiply(int a, int b) => a * b;
public double Divide(int a, int b) => (double)a / b;
}
Output:
Methods of Calculator class:
- Int32 Add(Int32 a, Int32 b)
- Int32 Subtract(Int32 a, Int32 b)
- Int32 Multiply(Int32 a, Int32 b)
- Double Divide(Int32 a, Int32 b)
Getting Fields
Type carType = typeof(Car);
FieldInfo[] fields = carType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("Fields of Car class:");
foreach (var field in fields)
{
string access = field.IsPublic ? "public" : field.IsPrivate ? "private" : "protected";
Console.WriteLine($"- {access} {field.FieldType.Name} {field.Name}");
}
// Sample Car class
public class Car
{
public string Make;
private string model;
protected int year;
}
Output:
Fields of Car class:
- public String Make
- private String model
- protected Int32 year
Working with Attributes
Attributes provide a powerful way to add metadata to your types. Reflection allows you to examine these attributes at runtime:
// Define a custom attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class VersionAttribute : Attribute
{
public string Version { get; }
public VersionAttribute(string version)
{
Version = version;
}
}
// Apply the attribute to a class
[Version("1.0.0")]
public class Product
{
public string Name { get; set; }
[Version("1.1.0")]
public void UpdateStock() { }
}
// Retrieve and display the attributes
Type productType = typeof(Product);
VersionAttribute classAttr = (VersionAttribute)Attribute.GetCustomAttribute(productType, typeof(VersionAttribute));
Console.WriteLine($"Class version: {classAttr?.Version}");
MethodInfo methodInfo = productType.GetMethod("UpdateStock");
VersionAttribute methodAttr = (VersionAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(VersionAttribute));
Console.WriteLine($"Method version: {methodAttr?.Version}");
Output:
Class version: 1.0.0
Method version: 1.1.0
Generic Type Information
Working with generic types requires special handling:
// Define a generic class
public class GenericRepository<T> where T : class
{
public void Add(T item) { }
public T GetById(int id) => null;
}
// Get and examine the generic type
Type genericType = typeof(GenericRepository<>);
Console.WriteLine($"Is generic type definition: {genericType.IsGenericTypeDefinition}");
Console.WriteLine($"Generic type parameters: {genericType.GetGenericArguments().Length}");
// Create a closed generic type
Type closedType = typeof(GenericRepository<Person>);
Console.WriteLine($"Closed type name: {closedType.Name}");
Console.WriteLine($"Is generic type: {closedType.IsGenericType}");
Console.WriteLine($"Generic type argument: {closedType.GetGenericArguments()[0].Name}");
Output:
Is generic type definition: True
Generic type parameters: 1
Closed type name: GenericRepository`1
Is generic type: True
Generic type argument: Person
Practical Example: Type Inspector
Let's create a practical utility that can inspect any object and display its type information:
public class TypeInspector
{
public static void InspectObject(object obj)
{
if (obj == null)
{
Console.WriteLine("Cannot inspect null object");
return;
}
Type type = obj.GetType();
Console.WriteLine($"Type Information for: {type.Name}");
Console.WriteLine($"Full Name: {type.FullName}");
Console.WriteLine($"Namespace: {type.Namespace}");
Console.WriteLine($"Assembly: {type.Assembly.GetName().Name}");
Console.WriteLine($"Base Type: {type.BaseType?.Name ?? "None"}");
Console.WriteLine("\nProperties:");
foreach (var prop in type.GetProperties())
{
object value = null;
try
{
value = prop.GetValue(obj);
}
catch
{
value = "Unable to access";
}
Console.WriteLine($"- {prop.Name}: {value} ({prop.PropertyType.Name})");
}
Console.WriteLine("\nMethods:");
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
string parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($"- {method.Name}({parameters})");
}
}
}
// Using the TypeInspector
Person person = new Person { Name = "John Doe", Age = 30, DateOfBirth = new DateTime(1993, 5, 15) };
TypeInspector.InspectObject(person);
Sample Output:
Type Information for: Person
Full Name: Person
Namespace: ConsoleApp
Assembly: ConsoleApp
Base Type: Object
Properties:
- Name: John Doe (String)
- Age: 30 (Int32)
- DateOfBirth: 5/15/1993 12:00:00 AM (DateTime)
Methods:
- ToString()
- Equals(Object obj)
- GetHashCode()
Real-World Applications
Type information and reflection are used extensively in many modern frameworks and libraries:
-
Dependency Injection Containers: DI frameworks like Microsoft.Extensions.DependencyInjection use reflection to scan assemblies, discover types, and automatically register services.
-
Object-Relational Mappers (ORMs): Entity Framework and other ORMs use reflection to map class properties to database columns.
-
Serialization/Deserialization: JSON.NET and System.Text.Json use reflection to convert objects to JSON strings and back.
-
Unit Testing Frameworks: NUnit and xUnit discover test methods through reflection.
-
Aspect-Oriented Programming: Frameworks that implement AOP often use reflection to dynamically intercept method calls.
Summary
Type information in C# provides a powerful mechanism to inspect and work with types dynamically at runtime:
- The
Type
class is the foundation of reflection in C# - You can obtain type information using
typeof()
,.GetType()
, orType.GetType()
- Type information allows you to examine properties, methods, fields, and attributes
- You can inspect the inheritance hierarchy and relationships between types
- Working with generic types requires specific approaches
- Reflection and type information enable powerful frameworks and libraries
While reflection is powerful, it does have performance implications compared to static code. Use it judiciously when dynamic behavior is truly needed or during development/debugging scenarios.
Additional Resources
- Official Microsoft Documentation on Reflection
- Type Class Documentation
- Reflection Performance Considerations
Exercises
- Create a simple class with various members (properties, fields, methods) and use reflection to list all its members.
- Write a method that takes a string representing a fully qualified type name and returns an instance of that type.
- Create an attribute to mark "obsolete" properties in a class and write code that uses reflection to find all properties marked with this attribute.
- Write a generic method that can compare two objects of the same type by comparing all their properties, using reflection.
- Create a simple serialization utility that uses reflection to convert an object to a dictionary and back.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)