Skip to main content

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

csharp
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

csharp
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

csharp
// 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:

csharp
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:

csharp
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

csharp
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

csharp
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

csharp
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:

csharp
// 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:

csharp
// 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:

csharp
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:

  1. Dependency Injection Containers: DI frameworks like Microsoft.Extensions.DependencyInjection use reflection to scan assemblies, discover types, and automatically register services.

  2. Object-Relational Mappers (ORMs): Entity Framework and other ORMs use reflection to map class properties to database columns.

  3. Serialization/Deserialization: JSON.NET and System.Text.Json use reflection to convert objects to JSON strings and back.

  4. Unit Testing Frameworks: NUnit and xUnit discover test methods through reflection.

  5. 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(), or Type.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

Exercises

  1. Create a simple class with various members (properties, fields, methods) and use reflection to list all its members.
  2. Write a method that takes a string representing a fully qualified type name and returns an instance of that type.
  3. Create an attribute to mark "obsolete" properties in a class and write code that uses reflection to find all properties marked with this attribute.
  4. Write a generic method that can compare two objects of the same type by comparing all their properties, using reflection.
  5. 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! :)