{"id":30424952,"url":"https://github.com/wieslawsoltes/ReactiveGenerator","last_synced_at":"2025-08-22T11:04:33.151Z","repository":{"id":263497657,"uuid":"890617803","full_name":"wieslawsoltes/ReactiveGenerator","owner":"wieslawsoltes","description":"ReactiveGenerator is a modern C# source generator that automates property change notification implementation, supporting both standard INotifyPropertyChanged and ReactiveUI patterns. ","archived":false,"fork":false,"pushed_at":"2025-01-16T18:27:45.000Z","size":417,"stargazers_count":82,"open_issues_count":4,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-08-01T14:12:49.962Z","etag":null,"topics":["csharp","generator","inotifypropertychanged","inpc","mvvm","property","reactive","reactiveui","source-generator"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wieslawsoltes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.TXT","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["wieslawsoltes"]}},"created_at":"2024-11-18T22:12:02.000Z","updated_at":"2025-07-29T15:22:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"9d7e958e-acbf-494e-9a56-b2ebe36093fb","html_url":"https://github.com/wieslawsoltes/ReactiveGenerator","commit_stats":{"total_commits":240,"total_committers":1,"mean_commits":240.0,"dds":0.0,"last_synced_commit":"516c889f9f073d9b70c37386331a848324d55a24"},"previous_names":["wieslawsoltes/reactivegenerator"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/wieslawsoltes/ReactiveGenerator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FReactiveGenerator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FReactiveGenerator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FReactiveGenerator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FReactiveGenerator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wieslawsoltes","download_url":"https://codeload.github.com/wieslawsoltes/ReactiveGenerator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wieslawsoltes%2FReactiveGenerator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271628166,"owners_count":24792821,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","generator","inotifypropertychanged","inpc","mvvm","property","reactive","reactiveui","source-generator"],"created_at":"2025-08-22T11:02:24.807Z","updated_at":"2025-08-22T11:04:33.134Z","avatar_url":"https://github.com/wieslawsoltes.png","language":"C#","funding_links":["https://github.com/sponsors/wieslawsoltes"],"categories":["Contributors Welcome for those"],"sub_categories":["1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category"],"readme":"# ReactiveGenerator\n\n[![CI](https://github.com/wieslawsoltes/ReactiveGenerator/actions/workflows/build.yml/badge.svg)](https://github.com/wieslawsoltes/ReactiveGenerator/actions/workflows/build.yml)\n[![NuGet](https://img.shields.io/nuget/v/ReactiveGenerator.svg)](https://www.nuget.org/packages/ReactiveGenerator)\n[![NuGet](https://img.shields.io/nuget/dt/ReactiveGenerator.svg)](https://www.nuget.org/packages/ReactiveGenerator)\n\nReactiveGenerator is a modern C# source generator that automates property change notification implementation, supporting both standard `INotifyPropertyChanged` and ReactiveUI patterns. It provides a robust, type-safe solution for generating reactive properties with comprehensive design-time support and code analysis capabilities.\n\n## Reducing Boilerplate\n\n### Before ReactiveGenerator\n\n```csharp\npublic class UserViewModel : ReactiveObject\n{\n    private string _firstName;\n    public string FirstName\n    {\n        get =\u003e _firstName;\n        set =\u003e this.RaiseAndSetIfChanged(ref _firstName, value);\n    }\n\n    private string _lastName;\n    public string LastName\n    {\n        get =\u003e _lastName;\n        set =\u003e this.RaiseAndSetIfChanged(ref _lastName, value);\n    }\n\n    private readonly ObservableAsPropertyHelper\u003cstring\u003e _fullName;\n    public string FullName =\u003e _fullName.Value;\n\n    private readonly ObservableAsPropertyHelper\u003cbool\u003e _hasName;\n    public bool HasName =\u003e _hasName.Value;\n\n    public UserViewModel()\n    {\n        _fullName = this.WhenAnyValue(x =\u003e x.FirstName, x =\u003e x.LastName)\n            .Select(tuple =\u003e $\"{tuple.Item1} {tuple.Item2}\".Trim())\n            .ToProperty(this, x =\u003e x.FullName);\n\n        _hasName = this.WhenAnyValue(x =\u003e x.FullName)\n            .Select(name =\u003e !string.IsNullOrEmpty(name))\n            .ToProperty(this, x =\u003e x.HasName);\n    }\n}\n```\n\n### After ReactiveGenerator\n\n```csharp\n[Reactive]\npublic partial class UserViewModel : ReactiveObject\n{\n    public partial string FirstName { get; set; }\n    public partial string LastName { get; set; }\n\n    [ObservableAsProperty]\n    public partial string FullName { get; }\n\n    [ObservableAsProperty]\n    public partial bool HasName { get; }\n\n    public UserViewModel()\n    {\n        // Generated extension methods make the code more readable\n        this.WhenAnyFirstName()\n            .CombineLatest(this.WhenAnyLastName())\n            .Select(names =\u003e $\"{names.First} {names.Second}\".Trim())\n            .ToProperty(this, x =\u003e x.FullName);\n\n        this.WhenAnyFullName()\n            .Select(name =\u003e !string.IsNullOrEmpty(name))\n            .ToProperty(this, x =\u003e x.HasName);\n    }\n}\n```\n\n## Quick Start Samples\n\n### Basic MVVM with Reactive Properties\n\n```csharp\n[Reactive]\npublic partial class UserViewModel : ReactiveObject\n{\n    // Simple properties - automatically implements INotifyPropertyChanged\n    public partial string FirstName { get; set; }\n    public partial string LastName { get; set; }\n    \n    // Computed property with ObservableAsPropertyHelper\n    [ObservableAsProperty]\n    public partial string FullName { get; }\n    \n    public UserViewModel()\n    {\n        // Use generated WhenAnyValue extension method\n        this.WhenAnyValue(x =\u003e x.FirstName, x =\u003e x.LastName)\n            .Select(tuple =\u003e $\"{tuple.Item1} {tuple.Item2}\".Trim())\n            .ToProperty(this, x =\u003e x.FullName);\n    }\n}\n```\n\n### Search Form Example\n\n```csharp\npublic partial class SearchViewModel : ReactiveObject\n{\n    // Reactive properties for user input\n    [Reactive]\n    public partial string SearchTerm { get; set; }\n\n    [Reactive]\n    public partial bool IsSearching { get; set; }\n\n    // Results as ObservableAsProperty\n    [ObservableAsProperty]\n    public partial IReadOnlyList\u003cSearchResult\u003e Results { get; }\n\n    public SearchViewModel()\n    {\n        // Use the generated extension method\n        this.WhenAnySearchTerm()\n            .Throttle(TimeSpan.FromMilliseconds(400))\n            .Do(_ =\u003e IsSearching = true)\n            .Select(term =\u003e PerformSearch(term))\n            .Do(_ =\u003e IsSearching = false)\n            .ToProperty(this, x =\u003e x.Results);\n    }\n}\n```\n\n### Form Validation Example\n\n```csharp\npublic partial class RegistrationForm : ReactiveObject\n{\n    [Reactive]\n    public partial string Email { get; set; }\n\n    [Reactive]\n    public partial string Password { get; set; }\n    \n    [ObservableAsProperty]\n    public partial bool IsValid { get; }\n    \n    [ObservableAsProperty]\n    public partial string ValidationMessage { get; }\n\n    public RegistrationForm()\n    {\n        // Combine multiple properties for validation\n        this.WhenAnyValue(x =\u003e x.Email, x =\u003e x.Password)\n            .Select(tuple =\u003e ValidateForm(tuple.Item1, tuple.Item2))\n            .ToProperty(this, x =\u003e x.ValidationMessage);\n\n        this.WhenAnyValue(x =\u003e x.ValidationMessage)\n            .Select(msg =\u003e string.IsNullOrEmpty(msg))\n            .ToProperty(this, x =\u003e x.IsValid);\n    }\n}\n```\n\n### Collections and Lists\n\n```csharp\npublic partial class TodoListViewModel : ReactiveObject\n{\n    [Reactive]\n    public partial ObservableCollection\u003cTodoItem\u003e Items { get; set; }\n\n    [Reactive]\n    public partial string NewItemText { get; set; }\n\n    [ObservableAsProperty]\n    public partial int CompletedCount { get; }\n\n    [ObservableAsProperty]\n    public partial int TotalCount { get; }\n\n    public TodoListViewModel()\n    {\n        Items = new ObservableCollection\u003cTodoItem\u003e();\n\n        // Track collection changes\n        this.WhenAnyValue(x =\u003e x.Items)\n            .Select(items =\u003e items.Count)\n            .ToProperty(this, x =\u003e x.TotalCount);\n\n        this.WhenAnyValue(x =\u003e x.Items)\n            .Select(items =\u003e items.Count(i =\u003e i.IsCompleted))\n            .ToProperty(this, x =\u003e x.CompletedCount);\n    }\n}\n\n[Reactive]\npublic partial class TodoItem\n{\n    public partial string Text { get; set; }\n    public partial bool IsCompleted { get; set; }\n}\n```\n\n## Key Features\n\n### Property Change Notifications\n- Automated `INotifyPropertyChanged` implementation\n- Support for ReactiveUI's `ReactiveObject` pattern\n- Efficient, cached `PropertyChangedEventArgs`\n- Thread-safe weak event handling\n- Smart code analysis with refactoring suggestions\n\n### ObservableAsPropertyHelper Generation\n- Automated generation of `ObservableAsPropertyHelper` properties\n- Simplifies ReactiveUI's observable-to-property pattern\n- Reduces boilerplate code for computed properties\n- Enhances readability and maintainability\n- Thread-safe implementation with weak event handling\n\n### Code Analysis \u0026 Fixes\n- Intelligent detection of convertible properties\n- Code fix providers for automatic conversion to reactive properties\n- Bulk refactoring support (file, project, and solution-wide)\n- Design-time validation and suggestions\n\n### Flexible Declaration Options\n- Class-level reactivity with `[Reactive]` attribute\n- Property-level granular control with `[Reactive]` and `[ObservableAsProperty]`\n- Selective opt-out using `[IgnoreReactive]`\n- Support for custom property implementations\n- Inheritance-aware property generation\n\n## Installation \u0026 Setup\n\n### NuGet Package\n```bash\ndotnet add package ReactiveGenerator\n```\n\n### Prerequisites\n- .NET 9.0+\n- C# 13.0+\n- Visual Studio 2022 or compatible IDE\n- Project configuration:\n  ```xml\n  \u003cLangVersion\u003epreview\u003c/LangVersion\u003e\n  ```\n\n## Advanced Usage\n\n### Selective Property Control\n```csharp\n[Reactive]\npublic partial class Shape\n{\n    public partial string Name { get; set; }         // Reactive\n\n    [IgnoreReactive]\n    public partial string Tag { get; set; }          // Non-reactive\n\n    // Custom implementation with backing field\n    private Color _color = Colors.White;\n    [IgnoreReactive]\n    public partial Color Color \n    {\n        get =\u003e _color;\n        set\n        {\n            if (_color != value)\n            {\n                _color = value;\n                OnPropertyChanged();\n            }\n        }\n    }\n}\n```\n\n### Inheritance Example\n\n```csharp\n[Reactive]\npublic partial class Animal\n{\n    public partial string Name { get; set; }\n}\n\n[Reactive]\npublic partial class Dog : Animal\n{\n    public partial string Breed { get; set; }\n    \n    [ObservableAsProperty]\n    public partial string DisplayName { get; }\n    \n    public Dog()\n    {\n        this.WhenAnyValue(x =\u003e x.Name, x =\u003e x.Breed)\n            .Select(t =\u003e $\"{t.Item1} ({t.Item2})\")\n            .ToProperty(this, x =\u003e x.DisplayName);\n    }\n}\n```\n\n## Configuration\n\n### MSBuild Properties\n```xml\n\u003cPropertyGroup\u003e\n    \u003c!-- Use explicit backing fields instead of field keyword --\u003e\n    \u003cUseBackingFields\u003etrue\u003c/UseBackingFields\u003e\n\u003c/PropertyGroup\u003e\n```\n\n## Troubleshooting\n\n### Common Issues\n\n#### Missing Partial Declarations\n```csharp\n// ❌ Incorrect\n[Reactive]\npublic class Example\n{\n    public string Property { get; set; }\n}\n\n// ✅ Correct\n[Reactive]\npublic partial class Example\n{\n    public partial string Property { get; set; }\n}\n```\n\n#### Incorrect ObservableAsProperty Usage\n```csharp\n// ❌ Incorrect\npublic class ViewModel : ReactiveObject\n{\n    [ObservableAsProperty]\n    public string ComputedValue { get; }\n}\n\n// ✅ Correct\npublic partial class ViewModel : ReactiveObject\n{\n    [ObservableAsProperty]\n    public partial string ComputedValue { get; }\n}\n```\n\n### Implementation Details\n\n#### Event Management\n- Uses `WeakEventManager\u003cTDelegate\u003e` for efficient memory management\n- Thread-safe event handling with concurrent collections\n- Automatic cleanup of unused subscriptions\n- Proper handling of generated `PropertyChangedEventArgs` instances\n\n#### Type Resolution\n- Cross-assembly type inheritance support\n- Full generic type constraint validation\n- Proper handling of nullable reference types\n- Support for nested type hierarchies\n\n#### Code Generation\n- Deterministic output for reliable builds\n- Support for source link and debugging\n- Efficient handling of large type hierarchies\n- Proper XML documentation generation\n\n### Known Limitations\n- Properties must be declared as `partial`\n- Classes must be declared as `partial`\n- Custom implementations require `[IgnoreReactive]` attribute\n- Base class implementations must be compatible with `INotifyPropertyChanged`\n- Generic type constraints must be valid at declaration site\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Submit a Pull Request\n\nFor major changes, please open an issue first to discuss the proposed changes.\n\n## License\n\nReactiveGenerator is licensed under the MIT license. See [LICENSE](LICENSE.TXT) file for details.\n\n## Contact\n\nFor questions, feedback, or issues, please open a GitHub issue.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwieslawsoltes%2FReactiveGenerator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwieslawsoltes%2FReactiveGenerator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwieslawsoltes%2FReactiveGenerator/lists"}