PropertyResolvers by Tom Biddulph
NuGet / site data
Details
Info
Name: PropertyResolvers
A source generator that creates property resolver classes for types with matching properties. Use assembly-level attributes to generate resolvers that can retrieve property values from objects at runtime.
Author: Tom Biddulph
NuGet: https://www.nuget.org/packages/PropertyResolvers/
You can find more details at https://github.com/tombiddulph/PropertyResolvers
Author
Tom Biddulph

Original Readme
PropertyResolvers
A C# source generator that creates type-safe property resolver classes. Instead of using reflection at runtime to extract property values from objects, PropertyResolvers generates compile-time switch expressions that efficiently resolve property values across multiple types.
Installation
dotnet add package PropertyResolvers
Quick Start
- Add assembly-level attributes to specify which properties you want resolvers for:
using PropertyResolvers.Attributes;
[assembly: GeneratePropertyResolver("AccountId")]
[assembly: GeneratePropertyResolver("TenantId")]
- The generator automatically finds all types with matching properties and generates resolver classes:
// Generated code (AccountIdResolver.g.cs)
public static class AccountIdResolver
{
public static string? GetAccountId(object? obj) => obj switch
{
global::MyApp.Order x => x.AccountId.ToString(),
global::MyApp.Customer x => x.AccountId.ToString(),
global::MyApp.Invoice x => x.AccountId.ToString(),
_ => null
};
}
- Use the generated resolvers in your code:
var order = new Order \{ AccountId = "ACC-123" };
var customer = new Customer \{ AccountId = "ACC-456" };
var product = new Product \{ Name = "Widget" }; // No AccountId property
var id1 = AccountIdResolver.GetAccountId(order); // "ACC-123"
var id2 = AccountIdResolver.GetAccountId(customer); // "ACC-456"
var id3 = AccountIdResolver.GetAccountId(product); // null
Use Cases
- Multi-tenant applications: Extract
TenantIdfrom any entity without reflection - Audit logging: Consistently retrieve identifier properties across different entity types
- Event sourcing: Extract aggregate IDs from various event types
- API responses: Normalize property access across different DTO types
Configuration Options
######### Basic Usage
[assembly: GeneratePropertyResolver("AccountId")]
######### Namespace Filtering
Include only specific namespaces:
[assembly: GeneratePropertyResolver("AccountId", IncludeNamespaces = new[] \{ "MyApp.Domain", "MyApp.Entities" })]
Exclude specific namespaces:
[assembly: GeneratePropertyResolver("AccountId", ExcludeNamespaces = new[] \{ "System", "Microsoft" })]
######### Multiple Resolvers
You can generate resolvers for multiple properties:
[assembly: GeneratePropertyResolver("AccountId", ExcludeNamespaces = new[] \{ "System", "Microsoft" })]
[assembly: GeneratePropertyResolver("TenantId", ExcludeNamespaces = new[] \{ "System", "Microsoft" })]
[assembly: GeneratePropertyResolver("EntityId", ExcludeNamespaces = new[] \{ "System", "Microsoft" })]
Attribute Reference
######### GeneratePropertyResolverAttribute
| Parameter | Type | Description |
|---|---|---|
propertyName | string | (Required) The name of the property to generate a resolver for. Case-insensitive matching. |
IncludeNamespaces | string[]? | Only include types from these namespaces (prefix match). |
ExcludeNamespaces | string[]? | Exclude types from these namespaces (prefix match). |
Analyzer
The package includes an analyzer that detects duplicate property resolver definitions:
// PR001: Property resolver for 'AccountId' is already defined
[assembly: GeneratePropertyResolver("AccountId")]
[assembly: GeneratePropertyResolver("AccountId")] // Error: duplicate
Duplicate detection is case-insensitive, so "AccountId" and "accountid" are considered duplicates.
How It Works
- The generator scans all assembly-level
GeneratePropertyResolverattributes - For each attribute, it finds all classes and structs with a matching property name
- It generates a static resolver class with a switch expression that pattern-matches on the object type
- The generated code uses
global::prefixes to avoid namespace conflicts
Generated Code Location
The generated resolver classes are placed in a namespace matching your assembly name. For example, if your assembly is MyApp, the resolvers will be in the MyApp namespace.
Requirements
- .NET Standard 2.0 compatible (works with .NET Framework 4.7.2+ and .NET Core 2.0+)
- C# 9.0 or later (for switch expressions in generated code)
- Visual Studio 2022 17.0+ or compatible IDE with Roslyn 4.0+ support
License
MIT
About
Generating code for switch for property in C#
How to use
Example (source csproj, source files)
- CSharp Project
- Data.cs
- Program.cs
This is the CSharp Project that references PropertyResolvers
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PropertyResolvers" Version="0.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="PropertyResolvers.Attributes" Version="0.0.9" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
This is the use of PropertyResolvers in Data.cs
using PropertyResolvers.Attributes;
[assembly: GeneratePropertyResolver("Name",IncludeNamespaces = ["PropSwitch"])]
namespace PropSwitch;
public class Person
{
public int Id \{ get; set; }
public string Name \{ get; set; \} = string.Empty;
}
public class College
{
public int Id \{ get; set; }
public string Name \{ get; set; \} = string.Empty;
}
This is the use of PropertyResolvers in Program.cs
using PropSwitch;
Person p=new() \{ Id = 1, Name = "Andrei" };
College c = new() \{ Id = 1, Name = "Mathematics" };
Console.WriteLine(NameResolver.GetName(p));
Console.WriteLine(NameResolver.GetName(c));
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- NameResolver.g.cs
- PropertyResolverRegistration.g.cs
// <auto-generated/>
#nullable enable
namespace PropSwitch;
public static class NameResolver
{
public static string? GetName(object? obj) => obj switch
{
global::PropSwitch.Person x => x.Name.ToString(),
global::PropSwitch.College x => x.Name.ToString(),
_ => null
};
}
// <auto-generated/>
#nullable enable
using System.Runtime.CompilerServices;
namespace PropSwitch;
[CompilerGenerated]
internal static class PropertyResolverModuleInitializer
{
[ModuleInitializer]
internal static void Initialize()
{
global::PropertyResolvers.Attributes.PropertyResolverRegistry.Register("Name", global::PropSwitch.NameResolver.GetName);
}
}
Useful
Download Example (.NET C#)
Share PropertyResolvers
https://ignatandrei.github.io/RSCG_Examples/v2/docs/PropertyResolvers
Category "EnhancementProject" has the following generators:
1 AssemblyMetadata
2026-04-02
2 AssemblyMetadata.Generators
2026-05-15
3 AssemblyVersionInfo
2025-07-28
4 AutoInvoke.Generator
2024-03-03
5 AutoSpectre
2024-02-24
6 Bennewitz.Ninja.AutoVersioning
2026-07-04
7 BuildInfo
2024-01-20
8 Credfeto.Version.Information.Generator
2024-11-05
9 Larcanum.GitInfo
2025-01-17
10 LinqGen.Generator
2024-03-04
11 Pekspro.BuildInformationGenerator
2024-07-19
12 PlantUmlClassDiagramGenerator
2024-02-20
13 PropertyResolvers
2026-08-26
14 RSCG_AMS
2023-04-16
15 RSCG_ExportDiagram
2024-08-01
16 RSCG_FunctionsWithDI
2023-04-16
17 RSCG_NameGenerator
2024-08-25
18 RSCG_TimeBombComment
2023-04-16
19 RSCG_Wait
2024-02-21
20 ShadowWriterProjectInfo
2025-07-27
21 ThisAssembly
2023-04-16
22 ThisAssembly.Constants
2024-07-18
23 ThisAssembly.Metadata
2024-07-20