The Kassyi.Generators.DependencyProperty is a zero-allocation, highly optimized generator for dotnet/runtime-scale codebases. It delivers microsecond-tier throughput and supports WPF, UWP, WinUI, Uno, Avalonia, and MAUI.
This project is an independently maintained, highly optimized fork of HavenDV/DependencyPropertyGenerator. We re-architected the generator to eliminate critical silent failures (such as emitting propertyChangedCallback: null without warnings, as seen in HavenDV#165), enforce strict compile-time type safety, and deliver up to 30% faster code generation (+62.4% overall throughput score) with zero Gen2 allocations.
Incremental source generators run continuously in the background on every keystroke. Heavy memory allocations in the original generator triggered Gen2 garbage collections, causing noticeable typing lag in IDEs like Visual Studio and JetBrains Rider.
We rebuilt the pipeline to maximize throughput:
- Zero Gen2 GC Pauses: Reduces memory allocations by ~22% (saving ~650 KB per class) to completely eliminate Gen2 GC pauses.
- Optimized Pipeline: Replaces expensive AST round-trips (
NormalizeWhitespace()) with direct, zero-allocation formatted streaming. - Zero-Allocation Scopes: Implements custom
ref structscope handlers (ClassScope) to prevent intermediate string allocations. - Declarative Rule Engine: Shifts from runtime string parsing to compile-time semantic flag analysis.
View Benchmark Results (AMD Ryzen 9 7900X / .NET 9)
| Metric | Upstream Baseline | This Fork (Phase 5) | Improvement |
|---|---|---|---|
| Initial Generation (WPF) | 5.349 ms (2.87 MB) | 3.729 ms (2.22 MB) | -30.3% time / -22.6% memory |
| Incremental Gen (WPF) | 7.176 ms (3.59 MB) | 5.663 ms (2.93 MB) | -21.1% time / -18.4% memory |
| Initial Generation (WinUI) | 5.720 ms (2.81 MB) | 4.192 ms (2.21 MB) | -26.7% time / -21.4% memory |
| Incremental Gen (WinUI) | 7.412 ms (3.55 MB) | 5.847 ms (2.94 MB) | -21.1% time / -17.2% memory |
| Initial Generation (Avalonia) | 5.282 ms (2.86 MB) | 4.137 ms (2.25 MB) | -21.7% time / -21.3% memory |
| Incremental Gen (Avalonia) | 7.103 ms (3.62 MB) | 5.665 ms (3.01 MB) | -20.2% time / -16.9% memory |
| Initial Generation (MAUI) | 5.533 ms (2.90 MB) | 4.147 ms (2.26 MB) | -25.0% time / -22.1% memory |
| Incremental Gen (MAUI) | 7.095 ms (3.67 MB) | 5.843 ms (3.02 MB) | -17.6% time / -17.7% memory |
| Overall Throughput Score | 1,000 pts (1,288 ops/s) | 1,624 pts (1,685 ops/s) | +62.4% Score Boost π |
| GC Gen2 (Initial) | 7.8β15.6 / 1k ops | 0.0000 / 1k ops | 100% Eliminated |
- Strict Type Safety (
#error DPG0001/DPG0007): The original generator silently failed (HavenDV#165), emittingpropertyChangedCallback: nullwithout diagnostic errors for invalidOnChangedcallbacks. This fork immediately surfaces signature mismatches as compile-time errors. - Target-Typed
new(...)Expansion: Supports C# 9.0+DefaultValueExpression = "new(...)". The generator automatically expands these into fully-qualified constructors to remove verbose type names. - Modular Architecture: Decouples monolithic logic into modular framework strategies (WPF, WinUI, Avalonia, etc.) to improve maintainability and testability.
- Package Renaming: Publishes as
Kassyi.Generators.DependencyPropertyto provide a clean namespace and prevent collisions.
Install the package via the .NET CLI or NuGet Package Manager:
dotnet add package Kassyi.Generators.DependencyPropertyOr add the package reference directly to your .csproj:
<PackageReference Include="Kassyi.Generators.DependencyProperty" Version="0.1.0" PrivateAssets="all" />Define your properties using generic attributes. The generator automatically wires up the boilerplate.
using DependencyPropertyGenerator;
using System.Windows.Controls;
#nullable enable
namespace MyApp.Controls;
[DependencyProperty<bool>("IsSpinning", DefaultValue = true, Category = "Category", Description = "Description")]
public partial class MyControl : UserControl
{
// Optional callback, automatically wired up by the generator
partial void OnIsSpinningChanged(bool oldValue, bool newValue)
{
}
}
[AttachedDependencyProperty<object, TreeView>("SelectedItem", DefaultBindingMode = DefaultBindingMode.TwoWay)]
public static partial class TreeViewExtensions
{
// Optional callback, automatically wired up by the generator
static partial void OnSelectedItemChanged(TreeView sender, object? oldValue, object? newValue)
{
}
}View the generated code
// HintName: MyControl.Properties.IsSpinning.generated.cs
#nullable enable
namespace MyApp.Controls
{
public partial class MyControl
{
public static readonly global::System.Windows.DependencyProperty IsSpinningProperty =
global::System.Windows.DependencyProperty.Register(
name: "IsSpinning",
propertyType: typeof(bool),
ownerType: typeof(global::MyApp.Controls.MyControl),
typeMetadata: new global::System.Windows.FrameworkPropertyMetadata(
defaultValue: (bool)true,
flags: global::System.Windows.FrameworkPropertyMetadataOptions.None,
propertyChangedCallback: static (sender, args) =>
{
((global::MyApp.Controls.MyControl)sender).OnIsSpinningChanged(
(bool)args.OldValue,
(bool)args.NewValue);
}));
[global::System.ComponentModel.Category("Category")]
[global::System.ComponentModel.Description("Description")]
public bool IsSpinning
{
get => (bool)GetValue(IsSpinningProperty);
set => SetValue(IsSpinningProperty, value);
}
partial void OnIsSpinningChanged();
partial void OnIsSpinningChanged(bool newValue);
partial void OnIsSpinningChanged(bool oldValue, bool newValue);
}
}(Note: Code shortened for brevity)
You can cleanly define default values using C# 9.0+ target-typed new() expressions. The generator safely expands it to the full type:
public readonly record struct Data(int Value);
// Automatically expands to the property's type (new global::MyNamespace.Data(42))
[AttachedDependencyProperty<Data, TreeView>("SelectedItem", DefaultValueExpression = "new(42)")]
// Also supports the default constructor:
[AttachedDependencyProperty<Data, TreeView>("SelectedItem", DefaultValueExpression = "new()")]The generator automatically manages properties tied to UI events:
[AttachedDependencyProperty<object, Grid>("BindEventProperty", BindEvent = nameof(Grid.MouseWheel), DefaultValueExpression = "new()")]
public static partial class GridExtensions
{
private static void OnBindEventPropertyChanged_MouseWheel(object? sender, System.Windows.Input.MouseWheelEventArgs args)
{
// Handle the mouse wheel event
}
}When the property value changes, it automatically unsubscribes the old value and subscribes the new value to sender.MouseWheel += OnBindEventPropertyChanged_MouseWheel;.
The easiest way to generate XML documentation is via the Description property:
[DependencyProperty<bool>("IsSpinning", Description = "Indicates whether the element is spinning.")]This adds the [Description] attribute and embeds the text directly into the generated XML docs. For raw XML, use the XmlDocumentation or PropertyXmlDocumentation properties.
View Generated XML Documentation Code
/// <summary>
/// Identifies the <see cref="IsSpinning"/> dependency property.<br/>
/// Default value: default(bool)
/// </summary>
public static readonly global::System.Windows.DependencyProperty IsSpinningProperty =
global::System.Windows.DependencyProperty.Register(...);
/// <summary>
/// Indicates whether the element is spinning.<br/>
/// Default value: default(bool)
/// </summary>
[global::System.ComponentModel.Description("Indicates whether the element is spinning.")]
public bool IsSpinning
{
get => (bool)GetValue(IsSpinningProperty);
set => SetValue(IsSpinningProperty, value);
}If automatic platform detection fails (e.g., when targeting multiple platforms or custom builds), you can explicitly set your target in the .csproj:
<PropertyGroup>
<DefineConstants>$(DefineConstants);HAS_WPF</DefineConstants>
<!-- <DefineConstants>$(DefineConstants);HAS_UNO</DefineConstants> -->
<!-- <DefineConstants>$(DefineConstants);HAS_UNO_WINUI</DefineConstants> -->
<!-- <DefineConstants>$(DefineConstants);HAS_AVALONIA</DefineConstants> -->
</PropertyGroup>For UWP, WinUI, and Uno, the generator creates a RegisterPropertyChangedCallbacks() method. You must call this method manually in your control's constructor to properly register the property change callbacks.
Important
C# Language Version Requirement (LangVersion)
To use generic attributes (e.g., [DependencyProperty<T>], [RoutedEvent<T>]), ensure your project's .csproj sets LangVersion to 11.0 or higher (or preview / latest):
<PropertyGroup>
<LangVersion>11.0</LangVersion> <!-- or preview / latest -->
</PropertyGroup>(Note: Non-generic attributes such as [DependencyProperty("Name", typeof(Type))] remain fully functional on older C# versions like C# 8.0+.)
Detailed architectural blueprints, zero-allocation design patterns, complexity models, and framework-specific code generation rules are available on the GitHub Wiki (or under the spec/ directory for local offline reading):
- π π Read on GitHub Wiki (Recommended)
- πΊπΈ English Specifications (
spec/en/):- Overview & Index
- 01. FAQ & Design Rationale: Architectural philosophy & zero-allocation rationale
- 02. Foundation & Domain: DTO structure, ubiquitous language & supported platforms
- 03. Pipeline Architecture: Incremental generator pipeline & caching strategy
- 04. Framework Strategies: Platform API mapping & generator extension guidelines
- 05. Code Synthesis & Performance:
SourceWriter(ClassScope), zero-allocation synthesis, & profiling - 06. Complexity Model: Worst-case complexity analysis & scaling limits
- 07. Test Specification: Test architecture, combinatorial matrix, & diagnostics
- 08. Diagnostics Reference: Causes and solutions for
DPG0000-DPG9999
- π―π΅ ζ₯ζ¬θͺδ»ζ§ζΈ (
spec/ja/):
- Bug Reports & Issues: GitHub Issues
- Discussions & Ideas: GitHub Discussions