Refleksja w C# jest zaawansowaną funkcją umożliwiającą uzyskiwanie informacji o typach (strukturach danych takich jak klasy, interfejsy oraz ich składowych: właściwościach i metodach) oraz dynamiczne tworzenie obiektów, odczytywanie i modyfikowanie ich danych, a także wywoływanie ich metod w czasie działania programu [1, 2].
Przestrzeń nazw System.Reflection
System.Reflection to przestrzeń nazw w .NET, która zawiera wszystkie klasy potrzebne do korzystania z mechanizmu refleksji. W tabeli poniżej przedstawiono kilka z nich wraz z definicjami z oficjalnej dokumentacji [3].
| Klasa | Zastosowanie |
| TypeInfo | Reprezentuje deklaracje typu dla typów klas, typów interfejsów, typów tablic, typów wartości, typów wyliczenia, parametrów typu, ogólnych definicji typów i otwartych lub zamkniętych skonstruowanych typów ogólnych. |
| PropertyInfo | Odnajduje atrybuty właściwości i zapewnia dostęp do metadanych właściwości. |
| MethodInfo | Odnajduje atrybuty metody i zapewnia dostęp do metadanych metody. |
| FieldInfo | Odnajduje atrybuty pola i zapewnia dostęp do metadanych pola. |
| ConstructorInfo | Odnajduje atrybuty konstruktora klasy i zapewnia dostęp do metadanych konstruktora. |
| Assembly | Reprezentuje zestaw, który jest zestawem wielokrotnego użytku, wersjonalnym i samoopisującym blok konstrukcyjny aplikacji środowiska uruchomieniowego języka wspólnego. |
Uzyskiwanie informacji o typach
TypeInfo służy do odczytywania podstawowych metadanych typu. Klasa ta udostępnia m.in. kolekcje takie jak DeclaredProperties, DeclaredMethods, DeclaredFields i DeclaredConstructors, które zawiertają odpowiednio obiekty PropertyInfo, MethodInfo, FieldInfo i ConstructorInfo [4]. Praktyczny przykład przedstawiający wykorzystanie obiektu TypeInfo znajduje się poniżej.
internal class Program
{
static void Main(string[] args)
{
TypeInfo info = typeof(Book).GetTypeInfo();
Console.WriteLine("Class Information\n");
Console.WriteLine("Properties:");
foreach (var prop in info.DeclaredProperties)
{
Console.WriteLine($"- {prop.Name} ({prop.PropertyType.Name})");
}
Console.WriteLine("\nMethods:");
foreach (var metoda in info.DeclaredMethods)
{
Console.WriteLine($"- {metoda.Name}");
}
Console.WriteLine("\nFields:");
foreach (var field in info.DeclaredFields)
{
Console.WriteLine($"- {field.Name} ({field.FieldType.Name})");
}
Console.WriteLine("\nConstructors:");
foreach (var ctor in info.DeclaredConstructors)
{
Console.WriteLine($"- {ctor.Name} z {ctor.GetParameters().Length} parametrami");
}
}
}
internal class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public void DisplayInfo()
{
Console.WriteLine($"Title: {Title}, Author: {Author}, Year: {Year}");
}
}

Dynamiczne tworzenie obiektu z wykorzystaniem refleksji
Wykorzystanie refleksji pozwala na dynamiczne tworzenie obiektów na dwa sposoby. Pierwszym z nich jest zastosowanie metody Activator.CreateInstance [5]. Natomiast drugim podejściem jest wywołanie konstruktora typu poprzez obiekt ConstructorInfo i jego metodę Invoke [6]. Stworzone w ten sposób obiekty działają w taki sam sposób jak te utworzone tradycyjnie. Oba sposoby zostały zaprezentowane w poniższym kodzie.
using System.Reflection;
namespace Reflection
{
internal class Program
{
static void Main(string[] args)
{
Type typ = typeof(Book);
// Use Activator to create an instance
object obj1 = Activator.CreateInstance(typ);
Book bookCreatedByActivator = (Book)obj1;
bookCreatedByActivator.Title = "Krzyżacy";
bookCreatedByActivator.Author = "Henryk Sienkiewicz";
bookCreatedByActivator.Year = 1899;
bookCreatedByActivator.DisplayInfo();
// Use ConstructorInfo to create an instance
ConstructorInfo ctor = typ.GetConstructor(Type.EmptyTypes);
object obj2 = ctor.Invoke(null);
Book bookCreatedByConstructorInfoInvoke = (Book)obj2;
bookCreatedByConstructorInfoInvoke.Title = "Pan Tadeusz";
bookCreatedByConstructorInfoInvoke.Author = "Adam Mickiewicz";
bookCreatedByConstructorInfoInvoke.Year = 1834;
bookCreatedByConstructorInfoInvoke.DisplayInfo();
}
}
internal class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public void DisplayInfo()
{
Console.WriteLine($"Title: {Title}, Author: {Author}, Year: {Year}");
}
}
}

Ładowanie i obsługa zewnętrznych bibliotek DLL
Refleksja pozwala również na dynamiczne ładowanie i obsługę zewnętrznych bibliotek DLL za pomocą klasy Assembly [7]. Po załadowaniu biblioteki można pobrać interesujący typ, utworzyć jego instancję i korzystać z klas i metod zdefiniowanych w pliku DLL tak jakby od początku był częścią projektu. Najlepiej zobaczyć to na przykładzie kodu dwóch projektów poniżej (biblioteki i programu konsolowego).
namespace Library
{
public class ClassInDLL
{
public int Add(int a, int b)
{
return a + b;
}
}
}
using System.Reflection;
namespace Reflection
{
internal class Program
{
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom("Library.dll");
Type type = assembly.GetType("Library.ClassInDLL");
object calc = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("Add");
object result = method.Invoke(calc, new object[] { 5, 7 });
Console.WriteLine($"Result: {result}");
}
}
}

Źródła:
[1] https://medium.com/@ravipatel.it/understanding-c-reflection-a-beginners-guide-28d17388a79c
[2] https://www.c-sharpcorner.com/UploadFile/009ee3/reflection-in-C-Sharp/
[3] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection?view=net-8.0
[4] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection.typeinfo?view=net-8.0
[5] https://learn.microsoft.com/pl-pl/dotnet/api/system.activator.createinstance?view=net-8.0
[6] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection.constructorinfo.invoke?view=net-8.0
[7] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection.assembly?view=net-8.0