Skip to content

Commit fac0227

Browse files
committed
Feature Add OutputScheduler to ReactiveCommand
1 parent c27bc5c commit fac0227

15 files changed

+255
-11
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023-2025 ReactiveUI
3+
Copyright (c) ReactiveUI 2023-2025
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
2525
/// The name of the CanExecute Observable of bool.
2626
/// </value>
2727
public string? CanExecute { get; init; }
28+
29+
/// <summary>
30+
/// Gets the output scheduler.
31+
/// </summary>
32+
/// <value>
33+
/// The output scheduler.
34+
/// </value>
35+
public string? OutputScheduler { get; init; }
2836
}
2937
#nullable restore
3038
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
2525
/// The name of the CanExecute Observable of bool.
2626
/// </value>
2727
public string? CanExecute { get; init; }
28+
29+
/// <summary>
30+
/// Gets the output scheduler.
31+
/// </summary>
32+
/// <value>
33+
/// The output scheduler.
34+
/// </value>
35+
public string? OutputScheduler { get; init; }
2836
}
2937
#nullable restore
3038
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
2525
/// The name of the CanExecute Observable of bool.
2626
/// </value>
2727
public string? CanExecute { get; init; }
28+
29+
/// <summary>
30+
/// Gets the output scheduler.
31+
/// </summary>
32+
/// <value>
33+
/// The output scheduler.
34+
/// </value>
35+
public string? OutputScheduler { get; init; }
2836
}
2937
#nullable restore
3038
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
2525
/// The name of the CanExecute Observable of bool.
2626
/// </value>
2727
public string? CanExecute { get; init; }
28+
29+
/// <summary>
30+
/// Gets the output scheduler.
31+
/// </summary>
32+
/// <value>
33+
/// The output scheduler.
34+
/// </value>
35+
public string? OutputScheduler { get; init; }
2836
}
2937
#nullable restore
3038
#pragma warning restore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//HintName: ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.cs
2+
// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
3+
// Licensed to the .NET Foundation under one or more agreements.
4+
// The .NET Foundation licenses this file to you under the MIT license.
5+
// See the LICENSE file in the project root for full license information.
6+
7+
using System;
8+
9+
// <auto-generated/>
10+
#pragma warning disable
11+
#nullable enable
12+
namespace ReactiveUI.SourceGenerators;
13+
14+
/// <summary>
15+
/// ReativeCommandAttribute.
16+
/// </summary>
17+
/// <seealso cref="Attribute" />
18+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
19+
internal sealed class ReactiveCommandAttribute : Attribute
20+
{
21+
/// <summary>
22+
/// Gets the can execute method or property.
23+
/// </summary>
24+
/// <value>
25+
/// The name of the CanExecute Observable of bool.
26+
/// </value>
27+
public string? CanExecute { get; init; }
28+
29+
/// <summary>
30+
/// Gets the output scheduler.
31+
/// </summary>
32+
/// <value>
33+
/// The output scheduler.
34+
/// </value>
35+
public string? OutputScheduler { get; init; }
36+
}
37+
#nullable restore
38+
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
2525
/// The name of the CanExecute Observable of bool.
2626
/// </value>
2727
public string? CanExecute { get; init; }
28+
29+
/// <summary>
30+
/// Gets the output scheduler.
31+
/// </summary>
32+
/// <value>
33+
/// The output scheduler.
34+
/// </value>
35+
public string? OutputScheduler { get; init; }
2836
}
2937
#nullable restore
3038
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,32 @@ private async Task Test3(string baseString)
134134
return TestHelper.TestPass(sourceCode);
135135
}
136136

137+
/// <summary>
138+
/// Froms the reactive command with output scheduler.
139+
/// </summary>
140+
/// <returns>A task to monitor the async.</returns>
141+
[Fact]
142+
public Task FromReactiveCommandWithOutputScheduler()
143+
{
144+
// Arrange: Setup the source code that matches the generator input expectations.
145+
const string sourceCode = """
146+
using System;
147+
using ReactiveUI;
148+
using ReactiveUI.SourceGenerators;
149+
using System.Reactive.Linq;
150+
using System.Threading.Tasks;
151+
namespace TestNs;
152+
public partial class TestVM : ReactiveObject
153+
{
154+
[ReactiveCommand(OutputScheduler = "RxApp.MainThreadScheduler")]
155+
private int Test1() => 10;
156+
}
157+
""";
158+
159+
// Act: Initialize the helper and run the generator. Assert: Verify the generated code.
160+
return TestHelper.TestPass(sourceCode);
161+
}
162+
137163
/// <summary>
138164
/// Froms the reactive command with nested classes.
139165
/// </summary>

src/ReactiveUI.SourceGenerators.Execute/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ public static class Program
1313
/// <summary>
1414
/// Defines the entry point of the application.
1515
/// </summary>
16-
public static void Main() => _ = TestViewModel.Instance;
16+
public static void Main() => Application.Run(new TestViewWinForms());
1717
}

src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System.Diagnostics.CodeAnalysis;
77
using System.Reactive;
8+
using System.Reactive.Concurrency;
89
using System.Reactive.Disposables;
910
using System.Reactive.Linq;
1011
using System.Reactive.Subjects;
@@ -28,6 +29,8 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis
2829
private readonly Subject<double?> _testSubject = new();
2930
private readonly Subject<double> _testNonNullSubject = new();
3031

32+
private IScheduler _scheduler = RxApp.MainThreadScheduler;
33+
3134
[property: JsonInclude]
3235
[DataMember]
3336
[ObservableAsProperty]
@@ -74,6 +77,8 @@ public TestViewModel()
7477
{
7578
Console.Out.WriteLine("Activated");
7679
_test11PropertyHelper = this.WhenAnyValue(x => x.Test12Property).ToProperty(this, x => x.Test11Property, out _).DisposeWith(disposables);
80+
GetDataCommand.Do(_ => Console.Out.WriteLine("GetDataCommand Executed")).Subscribe().DisposeWith(disposables);
81+
GetDataCommand.Execute().Subscribe().DisposeWith(disposables);
7782
});
7883

7984
Console.Out.WriteLine("MyReadOnlyProperty before init");
@@ -362,6 +367,10 @@ protected virtual void Dispose(bool disposing)
362367
[ReactiveCommand]
363368
private async Task<Point> Test10Async(int size, CancellationToken ct) => await Task.FromResult(new Point(size, size));
364369

365-
[ReactiveCommand(CanExecute = nameof(_observable))]
370+
[ReactiveCommand(CanExecute = nameof(_observable), OutputScheduler = nameof(_scheduler))]
366371
private void TestPrivateCanExecute() => Console.Out.WriteLine("TestPrivateCanExecute");
372+
373+
[ReactiveCommand]
374+
private Task<System.Collections.IEnumerable> GetData(CancellationToken ct) =>
375+
Task.FromResult<System.Collections.IEnumerable>(Array.Empty<System.Collections.IEnumerable>());
367376
}

0 commit comments

Comments
 (0)