I have been on a long journey with for over 15 years. And for the last two years, I’ve been teaching people C# in a local IT school. There are topics that cause a lot of confusion in C#. Reflection is one of those. C# According to , reflection in C# provides objects (of type ) that describe assemblies, modules, and types. It allows you to perform various dynamic operations, such as creating instances of types, binding types to existing objects, invoking methods, and accessing fields and properties. Reflection leverages metadata present in assemblies to enable these dynamic capabilities. Microsoft Type In real-life scenarios, reflection can be applied in the following cases: Create custom attributes and access them in your program's metadata. Serialization/deserialization. Dependency Injection. While there are dedicated libraries available for serialization, deserialization, and dependency injection in C# and the platform, understanding how these libraries utilize reflection can provide valuable insights into the inner workings of the language and platform. Additionally, leveraging custom attributes through reflection can offer new possibilities for designing flexible and extensible applications. .NET I hope this overview provides a good starting point to dive into the world of reflection and its applications. If you have any specific questions or if there's anything else I can assist you with, please let me know! Create custom attributes When it comes to creating custom attributes, it's important to understand their purpose and why you might choose to use them instead of alternative approaches. Custom attributes are metadata extensions that provide additional information to the compiler about elements in the program code at runtime. By marking types, methods, properties, or other code elements with custom attributes, you can convey specific information or instructions to other code that interacts with them. Let's consider a simple example where we want to create a custom attribute to represent a minimum value for a property. To create a custom attribute, you need to create a new class that inherits from the class. It is the convention to suffix the attribute class name with "Attribute." System.Attribute public class MinValueAttribute : Attribute { public int Value { get;} public MinValueAttribute(int value) => Value = value; } It is possible to apply the newly created attribute to a certain property of the class. Let’s create a class with property which is marked with . User Age MinValueAttribute public class User { public string Name { get; set; } [MinValue(18)] public int Age { get; set; } } Remember that attributes are useless until somebody uses them. It means that some piece of code has to verify if a specific attribute is applied to a particular entity and build some logic based on that. Let’s create a validator class that verifies if is applied and if the current state of the object satisfies minimum value logic. MinValueAttribute public static class MinValueValidator { public static bool Validate(User person) { var type = typeof(User); var properties = type.GetProperties(); foreach (var property in properties) { var attributes = property.GetCustomAttributes(false); foreach (var attr in attributes) { if (attr is MinValueAttribute minValueAttribute) { var propValue = property.GetValue(person, null); if (propValue == null || int.Parse(propValue.ToString()) < minValueAttribute.Value) { return false; } } } } return true; } } To get object of the particular class we can use 3 different approaches: with the help of operator, using method of the class, or using the static method . After that, we need to get all the properties of the type under inspection by calling the method. For each property, we need to call method and for each attribute check if it is a required one by using operator. If it is we can call method for the property object to get actually value of the property. Then it is possible to compare the actual property value and the for the . Type typeof GetType() Object Type.GetType() GetProperties() GetCustomAttributes() is GetValue() Value MinValueAttribute To run the validator we can use the following code: var user = new User() { Age = 45, Name = "John" }; Console.WriteLine("User is under 18: " + MinValueValidator.Validate(user)); It has to return string. User is under 18: True Serialization/deserialization Serialization converts object state into a transportable or persistent format. Deserialization reverses this process. Popular serialization formats include XML, JSON, and binary.. How can we utilize reflection for this purpose? Remember that reflection provides object that contains all the information of class members and their states. That is what we need to make serialization work. Let’s create a simple class and the method inside. Type XmlConvertor Serialize() public static class XmlConverter { public static string Serialize(object obj) { var sb = new StringBuilder(); var type = obj.GetType(); sb.AppendLine("<" + type.Name + ">"); var props = new List<PropertyInfo>(type.GetProperties()); foreach (var prop in props) { var propValue = prop.GetValue(obj, null); sb.AppendLine("\t<" + prop.Name + ">" + propValue + "</" + prop.Name + ">"); } sb.AppendLine("</" + type.Name + ">"); return sb.ToString(); } } All we need to do is to call method for the object and iterate through existing collection to retrieve their values using method. What is left is just to create a proper XML string. Let’s serialize the object we described earlier. GetProperties() Type PropertyInfo GetValue() User var obj = new User { Name = "John Doe", Age = 30 }; var s = XmlConverter.Serialize(obj); Console.WriteLine(s); Output: <User> <Name>John Doe</Name> <Age>30</Age> </User> Let’s write method for the opposite operation to convert XML into a C# object. It can be made generic to simplify the way we determine the type to deserialize. Deserialize() public static class XmlConverter { // Serialize() method is somewhere here public static T Deserialize<T>(string s) { // instantiate the object to be deserialized var assembly = typeof(T).Assembly; var obj = assembly.CreateInstance(typeof(T).FullName); if (obj == null) { throw new Exception($"Can't instantiate a new object of type '{typeof(T).FullName}'"); } // parse input string int XML document var doc = XDocument.Parse(s); if (doc.Root == null) { throw new ArgumentException("Can't parse specified xml string"); } // deserialize class members states foreach (var node in doc.Root.Elements()) { var property = obj.GetType() .GetProperty(node.Name.LocalName, BindingFlags.Public | BindingFlags.Instance); if (null != property && property.CanWrite) { if (typeof(int) == property.PropertyType) { property.SetValue(obj, int.Parse(node.Value)); } else if (typeof(string) == property.PropertyType) { property.SetValue(obj, node.Value); } // TODO: add other types conversion } } return (T)obj; } } First of all, we need to get the object from the type we want to deserialize. It provides method to create a new object from the specified type. Then we have to parse XML and iterate over the all elements. Since we have a very simple XML structure we just need to iterate over the internal nodes and use method to find a corresponding property of the deserialized object. After that, we can call method to set the actual value. The section with setting properties value could be quite bulky so I just wrote an example for the and types. Assembly CreateInstance() GetProperty() SetValue() int string Let’s verify the method. Deserialize() var stringToDecode = "<User><Name>John Doe</Name><Age>30</Age></User>"; var decodedUser = XmlConverter.Deserialize<User>(stringToDecode); Console.WriteLine("User name: " + decodedUser.Name); Console.WriteLine("User age: " + decodedUser.Age); It will bring the following output: User name: John Doe User age: 30 Dependency Injection Dependency Injection (DI) is a design pattern that encourages loose coupling between classes. It enables the passing of dependencies into a class from external sources, rather than having the class handle its dependencies internally. This approach enhances class modularity, testability, and maintainability. To understand the Dependency Injection pattern we need to see the participants it comprises of. The client class (dependent class) is a class that depends on the service class Client Class: The service class (dependency) is a class that provides service to the client class. Service Class: The injector class injects the service class object into the client class. Injector Class: The following figure illustrates the relationship between these classes: The IoC container creates an object of the specified class and also injects all the dependency objects through a constructor, a property, or a method at run time and disposes it at the appropriate time. There is a number of IoC container libraries implemented for the C#. But let’s create our one DI container to see how it utilizes reflection for its needs. We are going to introduce Client and Service classes, and the dependency between them through the interface. It is a good practice to use interfaces as a contract to interact between two objects to follow the Dependency Inversion principle. There is an interface for the service with the method. Process() public interface IService { void Process(); } The implementation of the interface is very simple just to do some action inside the method. IService public class Service : IService { public void Process() { System.Console.WriteLine("Call Process() method"); } } The client code uses as an abstraction between the Service and the Client. IService public class Client { private IService _service; public Client(IService service) { _service = service; } public void RunProcess() { _service.Process(); } } Let’s create a simple class and implement Dependency Injection mechanisms. Container public class Container { private Dictionary<Type, Type> _containerData = new Dictionary<Type, Type>(); public void Register<TType, TImplementation>() { _containerData.Add(typeof(TType), typeof(TImplementation)); } } Inside the container, we need a dictionary that contains the type we want to register as a key and the type that represents the implementation as a value. is a generic method that accepts two types, the type to be registered and the implementation for it. _containerData Register<TType, TImplementation>() public class Container { // ... public TType GetInstance<TType>() { var type = typeof(TType); if (type.GetConstructors().Length > 1) throw new Exception($"The type '{type.FullName}' should implement only one constructor"); ConstructorInfo ctor = type.GetConstructors()[0]; ParameterInfo[] parameters = ctor.GetParameters(); object[] constructorArgs = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { var parameterType = parameters[i].ParameterType; if (!_containerData.ContainsKey(parameterType)) throw new Exception($"Can't find implementation for the type '{parameterType.FullName}'"); var obj = Activator.CreateInstance(_containerData[parameterType]); if (obj != null) constructorArgs[i] = obj; } return (TType)ctor.Invoke(constructorArgs); } } To provide instances of the particular type we can write a new method. Inside this method, we need to find the object in the type we want to build and get an array of objects to know which parameters path into the constructor. After that, we could iterate over the array of parameters and instantiate each of them adding them to an array of objects variable. When all the parameters are instantiated we can call method of the constructor and path just created parameters. GetInstance<TType>() ConstructorInfo ParameterInfo constructorArgs Invoke() To verify the functionality of the DI container we can utilize the following code: var container = new Container(); container.Register<IService, Service>(); var client = container.GetInstance<Client>(); client.RunProcess(); You should get the following output: Call Process() method Summary We have only touched the surface of its power, but it can take you a long way in comprehending how widely used libraries operate and how reflection is utilized in everyday tasks. Many C# developers either do not utilize reflection at all or lack understanding of its potential applications. Feel free to delve into the documentation to familiarize yourself with the full range of capabilities offered by reflection.