Reflector
Reflection is powerful but slow, and it breaks AOT compilation. Shiny Reflector uses C# source generators to give you the same dynamic property access — without any runtime reflection. Just add an attribute, mark your class partial, and you get compile-time generated property enumeration, reading, writing, and JSON serialization.
| GitHub | |
| Downloads |
Features
Section titled “Features”- Enumerate properties with metadata (name, type, has setter)
- Read and write values using string keys — case-insensitive
- Indexer syntax for loose-typed access (
reflector["MyProperty"] = 123) - Built-in
System.Text.Jsonconverter that serializes through the generated reflectors - Opt-in fallback to true reflection for non-attributed types
- Assembly info generation — capture build variables as compile-time constants
- Works with classes, records, and MVVM Community Toolkit source generation
-
Install the NuGet package:
Terminal window dotnet add package Shiny.Extensions.Reflector -
Add the
[Reflector]attribute to your class and mark it aspartial:using Shiny.Extensions.Reflector;[Reflector]public partial class MyModel{public string Name { get; set; }public int? Age { get; set; }public DateTime CreatedAt { get; set; }} -
Use
GetReflector()to access properties dynamically:var model = new MyModel { Name = "Hello", Age = 25 };var reflector = model.GetReflector();
Trimming and Native AOT
Section titled “Trimming and Native AOT”GetReflector() — the parameterless overload — only ever returns a source-generated reflector, so it is trim
and AOT safe. That is the path you want in a trimmed or Native AOT app: mark every type you reflect over with
[Reflector].
The runtime reflection escape hatches are annotated so the trimmer tells you when you leave that path:
| API | Annotation |
|---|---|
GetReflector() |
none — trim and AOT safe |
GetReflector(bool fallbackToTrueReflection) |
[RequiresUnreferencedCode] |
TrueReflectionReflectorClass |
[RequiresUnreferencedCode] |
ReflectorJsonConverter / ReflectorJsonConverter<T> |
[RequiresUnreferencedCode], [RequiresDynamicCode] |
See JSON Serialization for the details on each.
Using the Reflector
Section titled “Using the Reflector”Property Enumeration
Section titled “Property Enumeration”var reflector = model.GetReflector();
foreach (var prop in reflector.Properties){ var value = reflector[prop.Name]; Console.WriteLine($"{prop.Name} ({prop.Type.Name}) HasSetter={prop.HasSetter} Value={value}");}Reading Values
Section titled “Reading Values”// Typed accessvar name = reflector.GetValue<string>("Name");
// Indexer access (case-insensitive)var age = reflector["age"];
// Safe access with TryGetif (reflector.TryGetValue<string>("Name", out var value)) Console.WriteLine($"Name = {value}");Writing Values
Section titled “Writing Values”// Typed setterreflector.SetValue("Name", "Updated");
// Indexer setterreflector["Age"] = 30;Dynamic Property Copying
Section titled “Dynamic Property Copying”var source = sourceObject.GetReflector();var target = targetObject.GetReflector();
foreach (var prop in source.Properties){ if (target.HasProperty(prop.Name)) target.TrySetValue(prop.Name, source[prop.Name]);}Records
Section titled “Records”Records are fully supported — just use partial and the attribute:
[Reflector]public partial record MyRecord(string Name, int Age);

