Skip to content

Commit d76068c

Browse files
authored
Update command naming guidelines for ReactiveUI (#922)
Expanded documentation to recommend using the 'Command' suffix for ReactiveCommand properties, especially when using ReactiveUI.SourceGenerators. Added modern examples, migration advice for legacy code, and best practices for naming commands and implementation methods.
1 parent 8ba072f commit d76068c

File tree

1 file changed

+244
-4
lines changed

1 file changed

+244
-4
lines changed
Lines changed: 244 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,254 @@
11
# Command Names
22

3-
Don't suffix `ReactiveCommand` properties' names with `Command`; instead, name the property using a verb that describes the command's action. For example:
3+
## Modern Approach (Recommended)
4+
5+
With **ReactiveUI.SourceGenerators**, commands are automatically generated with a `Command` suffix. This is the recommended naming convention for modern ReactiveUI applications.
6+
7+
### Using ReactiveUI.SourceGenerators ?
8+
9+
When using the `[ReactiveCommand]` attribute, the generator automatically creates a property with the `Command` suffix:
10+
11+
```csharp
12+
using ReactiveUI;
13+
using ReactiveUI.SourceGenerators;
14+
15+
public partial class MyViewModel : ReactiveObject
16+
{
17+
// Method name: Synchronize
18+
// Generated property: SynchronizeCommand
19+
[ReactiveCommand]
20+
private async Task Synchronize()
21+
{
22+
await SynchronizeImpl(mergeInsteadOfRebase: !IsAhead);
23+
}
24+
25+
private async Task SynchronizeImpl(bool mergeInsteadOfRebase)
26+
{
27+
// Implementation here
28+
}
29+
}
30+
31+
// Usage in view:
32+
this.BindCommand(ViewModel, vm => vm.SynchronizeCommand, v => v.SyncButton);
33+
```
34+
35+
**Benefits**:
36+
- ? Consistent naming convention
37+
- ? Clear distinction between methods and commands
38+
- ? Matches platform conventions (WPF, MAUI)
39+
- ? Generated code is predictable
40+
- ? Less boilerplate code
41+
42+
### Standard ReactiveCommand Pattern
43+
44+
When creating commands manually (without source generators), **suffix command properties with `Command`**:
445

46+
```csharp
47+
using ReactiveUI;
48+
using System.Reactive;
49+
50+
public class MyViewModel : ReactiveObject
51+
{
52+
// Property name includes 'Command' suffix
53+
public ReactiveCommand<Unit, Unit> SynchronizeCommand { get; }
54+
55+
public MyViewModel()
56+
{
57+
SynchronizeCommand = ReactiveCommand.CreateFromTask(
58+
() => SynchronizeImpl(mergeInsteadOfRebase: !IsAhead));
59+
}
60+
61+
private async Task SynchronizeImpl(bool mergeInsteadOfRebase)
62+
{
63+
// Implementation here
64+
}
65+
}
66+
67+
// Usage in view:
68+
this.BindCommand(ViewModel, vm => vm.SynchronizeCommand, v => v.SyncButton);
69+
```
70+
71+
## Naming Conventions
72+
73+
### Command Property Names
74+
75+
Use verbs that describe the command's action, with `Command` suffix:
76+
77+
```csharp
78+
// Good ?
79+
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
80+
public ReactiveCommand<Unit, Unit> DeleteCommand { get; }
81+
public ReactiveCommand<Unit, Unit> RefreshCommand { get; }
82+
public ReactiveCommand<string, Unit> SearchCommand { get; }
83+
84+
// Avoid ?
85+
public ReactiveCommand<Unit, Unit> Save { get; } // Missing 'Command' suffix
86+
public ReactiveCommand<Unit, Unit> PerformSave { get; } // Unclear naming
87+
```
88+
89+
### Implementation Method Names
90+
91+
When implementation is too complex for inline code, suffix the method with `Impl`:
92+
93+
```csharp
94+
public partial class MyViewModel : ReactiveObject
95+
{
96+
[ReactiveCommand]
97+
private async Task Save()
98+
{
99+
await SaveImpl();
100+
}
101+
102+
[ReactiveCommand]
103+
private async Task Delete()
104+
{
105+
await DeleteImpl();
106+
}
107+
108+
// Implementation methods
109+
private async Task SaveImpl() { /* ... */ }
110+
private async Task DeleteImpl() { /* ... */ }
111+
}
112+
```
113+
114+
## Complete Examples
115+
116+
### Example 1: Simple Command with Source Generators
117+
118+
```csharp
119+
public partial class MainViewModel : ReactiveObject
120+
{
121+
[Reactive]
122+
private string _searchText = string.Empty;
123+
124+
// Generates: public ReactiveCommand<Unit, Unit> SearchCommand { get; }
125+
[ReactiveCommand]
126+
private async Task Search()
127+
{
128+
var results = await SearchImpl(SearchText);
129+
Results = results;
130+
}
131+
132+
private async Task<List<SearchResult>> SearchImpl(string searchText)
133+
{
134+
// Actual search implementation
135+
return await _searchService.SearchAsync(searchText);
136+
}
137+
}
138+
```
139+
140+
### Example 2: Command with Parameter
141+
142+
```csharp
143+
public partial class ItemViewModel : ReactiveObject
144+
{
145+
// Generates: public ReactiveCommand<Item, Unit> DeleteItemCommand { get; }
146+
[ReactiveCommand]
147+
private async Task DeleteItem(Item item)
148+
{
149+
await DeleteItemImpl(item);
150+
}
151+
152+
private async Task DeleteItemImpl(Item item)
153+
{
154+
await _repository.DeleteAsync(item);
155+
}
156+
}
157+
```
158+
159+
### Example 3: Command with CanExecute
160+
161+
```csharp
162+
public partial class EditViewModel : ReactiveObject
163+
{
164+
[Reactive]
165+
private bool _isValid;
166+
167+
private IObservable<bool> _canSave;
168+
169+
public EditViewModel()
170+
{
171+
_canSave = this.WhenAnyValue(x => x.IsValid);
172+
}
173+
174+
// Generates: public ReactiveCommand<Unit, Unit> SaveCommand { get; }
175+
[ReactiveCommand(CanExecute = nameof(_canSave))]
176+
private async Task Save()
177+
{
178+
await SaveImpl();
179+
}
180+
181+
private async Task SaveImpl()
182+
{
183+
await _dataService.SaveAsync(Data);
184+
}
185+
}
186+
```
187+
188+
### Example 4: Manual Command Creation
189+
190+
```csharp
191+
public class LegacyViewModel : ReactiveObject
192+
{
193+
// When not using source generators, explicitly include 'Command' suffix
194+
public ReactiveCommand<Unit, Unit> LoadDataCommand { get; }
195+
public ReactiveCommand<string, Unit> FilterCommand { get; }
196+
public ReactiveCommand<Unit, Unit> ClearCommand { get; }
197+
198+
public LegacyViewModel()
199+
{
200+
LoadDataCommand = ReactiveCommand.CreateFromTask(LoadDataImpl);
201+
202+
FilterCommand = ReactiveCommand.Create<string>(filter =>
203+
FilterImpl(filter));
204+
205+
var canClear = this.WhenAnyValue(x => x.HasData);
206+
ClearCommand = ReactiveCommand.Create(ClearImpl, canClear);
207+
}
208+
209+
private async Task LoadDataImpl() { /* ... */ }
210+
private void FilterImpl(string filter) { /* ... */ }
211+
private void ClearImpl() { /* ... */ }
212+
}
213+
```
214+
215+
## Legacy Code Warning
216+
217+
> **Note**: Older ReactiveUI code may not follow the `Command` suffix convention. When modernizing legacy code:
218+
> 1. Update property names to include `Command` suffix
219+
> 2. Migrate to ReactiveUI.SourceGenerators where possible
220+
> 3. Update view bindings to reference new property names
221+
222+
### Migration Example
223+
224+
**Before (Legacy)**:
5225
```csharp
6226
public ReactiveCommand Synchronize { get; private set; }
7227

8-
// and then in the ctor:
9228
Synchronize = ReactiveCommand.CreateFromObservable(
10-
_ => SynchronizeImpl(mergeInsteadOfRebase: !IsAhead));
229+
_ => SynchronizeImpl(mergeInsteadOfRebase: !IsAhead));
230+
```
11231

232+
**After (Modern)**:
233+
```csharp
234+
[ReactiveCommand]
235+
private async Task Synchronize()
236+
{
237+
await SynchronizeImpl(mergeInsteadOfRebase: !IsAhead);
238+
}
12239
```
13240

14-
When a `ReactiveCommand`'s implementation is too large or too complex for an anonymous delegate, name the implementation's method the same name as the command, but with `Impl` suffixed (for example, `SychronizeImpl` above).
241+
## Best Practices
242+
243+
1. **Always use `Command` suffix** - Makes commands easily identifiable
244+
2. **Use source generators** - Reduces boilerplate and ensures consistency
245+
3. **Verb-based names** - Clearly describe the action (Save, Delete, Refresh)
246+
4. **Suffix complex implementations with `Impl`** - Separates interface from implementation
247+
5. **Consistent casing** - Use PascalCase for command properties
248+
249+
## Related Topics
250+
251+
- [SourceGenerators Guide](~/docs/handbook/view-models/boilerplate-code.md)
252+
- [Commands Handbook](~/docs/handbook/commands/index.md)
253+
- [View Models](~/docs/handbook/view-models/index.md)
254+
- [Data Binding](~/docs/handbook/data-binding/index.md)

0 commit comments

Comments
 (0)