diff --git a/LICENSE b/LICENSE
index cab1c93..f15da85 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 ReactiveUI
+Copyright (c) ReactiveUI 2023-2025
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 6511b43..9997a4f 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ ReactiveUI Source Generators automatically generate ReactiveUI objects to stream
- `[ObservableAsProperty(PropertyName = "ReadOnlyPropertyName")]`
- `[ReactiveCommand]`
- `[ReactiveCommand(CanExecute = nameof(IObservableBoolName))]` with CanExecute
+- `[ReactiveCommand(OutputScheduler = "RxApp.MainThreadScheduler")]` using a ReactiveUI Scheduler
+- `[ReactiveCommand(OutputScheduler = nameof(_isheduler))]` using a Scheduler defined in the class
- `[ReactiveCommand][property: AttributeToAddToCommand]` with Attribute passthrough
- `[IViewFor(nameof(ViewModelName))]`
- `[RoutedControlHost("YourNameSpace.CustomControl")]`
@@ -411,6 +413,30 @@ public partial class MyReactiveClass
}
```
+### Usage ReactiveCommand with ReactiveUI OutputScheduler
+```csharp
+using ReactiveUI.SourceGenerators;
+
+public partial class MyReactiveClass
+{
+ [ReactiveCommand(OutputScheduler = "RxApp.MainThreadScheduler")]
+ private void Execute() { }
+}
+```
+
+### Usage ReactiveCommand with custom OutputScheduler
+```csharp
+using ReactiveUI.SourceGenerators;
+
+public partial class MyReactiveClass
+{
+ private IScheduler _customScheduler = new TestScheduler();
+
+ [ReactiveCommand(OutputScheduler = nameof(_customScheduler))]
+ private void Execute() { }
+}
+```
+
## Usage IViewFor `[IViewFor(nameof(ViewModelName))]`
### IViewFor usage
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 92df71c..d2aeb2b 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
/// The name of the CanExecute Observable of bool.
///
public string? CanExecute { get; init; }
+
+ ///
+ /// Gets the output scheduler.
+ ///
+ ///
+ /// The output scheduler.
+ ///
+ public string? OutputScheduler { get; init; }
}
#nullable restore
#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 92df71c..d2aeb2b 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
/// The name of the CanExecute Observable of bool.
///
public string? CanExecute { get; init; }
+
+ ///
+ /// Gets the output scheduler.
+ ///
+ ///
+ /// The output scheduler.
+ ///
+ public string? OutputScheduler { get; init; }
}
#nullable restore
#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 92df71c..d2aeb2b 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
/// The name of the CanExecute Observable of bool.
///
public string? CanExecute { get; init; }
+
+ ///
+ /// Gets the output scheduler.
+ ///
+ ///
+ /// The output scheduler.
+ ///
+ public string? OutputScheduler { get; init; }
}
#nullable restore
#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 92df71c..d2aeb2b 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
/// The name of the CanExecute Observable of bool.
///
public string? CanExecute { get; init; }
+
+ ///
+ /// Gets the output scheduler.
+ ///
+ ///
+ /// The output scheduler.
+ ///
+ public string? OutputScheduler { get; init; }
}
#nullable restore
#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithOutputScheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithOutputScheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
new file mode 100644
index 0000000..d2aeb2b
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithOutputScheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -0,0 +1,38 @@
+//HintName: ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.cs
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System;
+
+//
+#pragma warning disable
+#nullable enable
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// ReativeCommandAttribute.
+///
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+internal sealed class ReactiveCommandAttribute : Attribute
+{
+ ///
+ /// Gets the can execute method or property.
+ ///
+ ///
+ /// The name of the CanExecute Observable of bool.
+ ///
+ public string? CanExecute { get; init; }
+
+ ///
+ /// Gets the output scheduler.
+ ///
+ ///
+ /// The output scheduler.
+ ///
+ public string? OutputScheduler { get; init; }
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 92df71c..d2aeb2b 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -25,6 +25,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
/// The name of the CanExecute Observable of bool.
///
public string? CanExecute { get; init; }
+
+ ///
+ /// Gets the output scheduler.
+ ///
+ ///
+ /// The output scheduler.
+ ///
+ public string? OutputScheduler { get; init; }
}
#nullable restore
#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs
index 4331301..9e77a8c 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs
@@ -134,6 +134,32 @@ private async Task Test3(string baseString)
return TestHelper.TestPass(sourceCode);
}
+ ///
+ /// Froms the reactive command with output scheduler.
+ ///
+ /// A task to monitor the async.
+ [Fact]
+ public Task FromReactiveCommandWithOutputScheduler()
+ {
+ // Arrange: Setup the source code that matches the generator input expectations.
+ const string sourceCode = """
+ using System;
+ using ReactiveUI;
+ using ReactiveUI.SourceGenerators;
+ using System.Reactive.Linq;
+ using System.Threading.Tasks;
+ namespace TestNs;
+ public partial class TestVM : ReactiveObject
+ {
+ [ReactiveCommand(OutputScheduler = "RxApp.MainThreadScheduler")]
+ private int Test1() => 10;
+ }
+ """;
+
+ // Act: Initialize the helper and run the generator. Assert: Verify the generated code.
+ return TestHelper.TestPass(sourceCode);
+ }
+
///
/// Froms the reactive command with nested classes.
///
diff --git a/src/ReactiveUI.SourceGenerators.Execute/Program.cs b/src/ReactiveUI.SourceGenerators.Execute/Program.cs
index ec0abc4..76c9db9 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/Program.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/Program.cs
@@ -13,5 +13,5 @@ public static class Program
///
/// Defines the entry point of the application.
///
- public static void Main() => _ = TestViewModel.Instance;
+ public static void Main() => Application.Run(new TestViewWinForms());
}
diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
index 02aed1c..d893d97 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
@@ -5,6 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Reactive;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
@@ -28,6 +29,8 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis
private readonly Subject _testSubject = new();
private readonly Subject _testNonNullSubject = new();
+ private IScheduler _scheduler = RxApp.MainThreadScheduler;
+
[property: JsonInclude]
[DataMember]
[ObservableAsProperty]
@@ -74,6 +77,8 @@ public TestViewModel()
{
Console.Out.WriteLine("Activated");
_test11PropertyHelper = this.WhenAnyValue(x => x.Test12Property).ToProperty(this, x => x.Test11Property, out _).DisposeWith(disposables);
+ GetDataCommand.Do(_ => Console.Out.WriteLine("GetDataCommand Executed")).Subscribe().DisposeWith(disposables);
+ GetDataCommand.Execute().Subscribe().DisposeWith(disposables);
});
Console.Out.WriteLine("MyReadOnlyProperty before init");
@@ -362,6 +367,10 @@ protected virtual void Dispose(bool disposing)
[ReactiveCommand]
private async Task Test10Async(int size, CancellationToken ct) => await Task.FromResult(new Point(size, size));
- [ReactiveCommand(CanExecute = nameof(_observable))]
+ [ReactiveCommand(CanExecute = nameof(_observable), OutputScheduler = nameof(_scheduler))]
private void TestPrivateCanExecute() => Console.Out.WriteLine("TestPrivateCanExecute");
+
+ [ReactiveCommand]
+ private Task GetData(CancellationToken ct) =>
+ Task.FromResult(Array.Empty());
}
diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs
index 6abc7d7..d022952 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs
@@ -21,6 +21,7 @@ public TestViewWinForms()
{
InitializeComponent();
ViewModel = TestViewModel.Instance;
+ ViewModel.Activator.Activate();
}
}
}
diff --git a/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs
index bbce0d7..11d65c6 100644
--- a/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs
+++ b/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs
@@ -82,6 +82,12 @@ internal sealed class ReactiveObjectAttribute : Attribute;
public const string ReactiveCommandAttributeType = "ReactiveUI.SourceGenerators.ReactiveCommandAttribute";
+ ///
+ /// Gets the reactive command attribute.
+ ///
+ ///
+ /// The reactive command attribute.
+ ///
public static string ReactiveCommandAttribute => $$"""
// Copyright (c) {{DateTime.Now.Year}} .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
@@ -110,6 +116,14 @@ internal sealed class ReactiveCommandAttribute : Attribute
/// The name of the CanExecute Observable of bool.
///
public string? CanExecute { get; init; }
+
+ ///
+ /// Gets the output scheduler.
+ ///
+ ///
+ /// The output scheduler.
+ ///
+ public string? OutputScheduler { get; init; }
}
#nullable restore
#pragma warning restore
diff --git a/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs b/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs
index 7efdd1f..585320b 100644
--- a/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs
+++ b/src/ReactiveUI.SourceGenerators/Core/Extensions/ITypeSymbolExtensions.cs
@@ -172,6 +172,23 @@ public static bool IsObservableReturnType(this ITypeSymbol? typeSymbol)
return false;
}
+ public static bool IsIShedulerType(this ITypeSymbol? typeSymbol)
+ {
+ var nameFormat = SymbolDisplayFormat.FullyQualifiedFormat;
+ do
+ {
+ var typeName = typeSymbol?.ToDisplayString(nameFormat);
+ if (typeName == "global::System.Reactive.Concurrency.IScheduler")
+ {
+ return true;
+ }
+
+ typeSymbol = typeSymbol?.BaseType;
+ }
+ while (typeSymbol != null);
+ return false;
+ }
+
public static bool IsObservableBoolType(this ITypeSymbol? typeSymbol)
{
var nameFormat = SymbolDisplayFormat.FullyQualifiedFormat;
diff --git a/src/ReactiveUI.SourceGenerators/ReactiveCommand/Models/CommandInfo.cs b/src/ReactiveUI.SourceGenerators/ReactiveCommand/Models/CommandInfo.cs
index 3f4a5e9..f8ffc46 100644
--- a/src/ReactiveUI.SourceGenerators/ReactiveCommand/Models/CommandInfo.cs
+++ b/src/ReactiveUI.SourceGenerators/ReactiveCommand/Models/CommandInfo.cs
@@ -18,6 +18,7 @@ internal record CommandInfo(
bool IsObservable,
string? CanExecuteObservableName,
CanExecuteTypeInfo? CanExecuteTypeInfo,
+ string? OutputScheduler,
EquatableArray ForwardedPropertyAttributes)
{
private const string UnitTypeName = "global::System.Reactive.Unit";
diff --git a/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs
index 592186e..21ec950 100644
--- a/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs
@@ -35,6 +35,9 @@ public partial class ReactiveCommandGenerator
private const string CreateO = ".CreateFromObservable";
private const string CreateT = ".CreateFromTask";
private const string CanExecute = "CanExecute";
+ private const string OutputScheduler = "OutputScheduler";
+ private const string MainThreadScheduler = "RxApp.MainThreadScheduler";
+ private const string TaskpoolScheduler = "RxApp.TaskpoolScheduler";
private static CommandInfo? GetMethodInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
{
@@ -83,6 +86,10 @@ public partial class ReactiveCommandGenerator
token.ThrowIfCancellationRequested();
+ TryGetOutputScheduler(methodSymbol, attributeData, out var outputScheduler);
+
+ token.ThrowIfCancellationRequested();
+
var methodSyntax = (MethodDeclarationSyntax)context.TargetNode;
methodSymbol.GatherForwardedAttributesFromMethod(context.SemanticModel, methodSyntax, token, out var attributes);
var forwardedPropertyAttributes = attributes.Select(static a => a.ToString()).ToImmutableArray();
@@ -103,6 +110,7 @@ public partial class ReactiveCommandGenerator
isObservable,
canExecuteObservableName,
canExecuteTypeInfo,
+ outputScheduler,
forwardedPropertyAttributes);
}
@@ -186,43 +194,46 @@ private static string GetCommandSyntax(CommandInfo commandExtensionInfo)
return
$$"""
- private ReactiveUI.ReactiveCommand<{{inputType}}, {{outputType}}>? {{fieldName}};
+ private {{RxCmd}}<{{inputType}}, {{outputType}}>? {{fieldName}};
{{forwardedPropertyAttributesString}}
- public ReactiveUI.ReactiveCommand<{{inputType}}, {{outputType}}> {{commandName}} { get => {{initializer}} }
+ public {{RxCmd}}<{{inputType}}, {{outputType}}> {{commandName}} { get => {{initializer}} }
""";
static string GenerateBasicCommand(CommandInfo commandExtensionInfo, string fieldName)
{
var commandType = commandExtensionInfo.IsObservable ? CreateO : commandExtensionInfo.IsTask ? CreateT : Create;
+ var outputScheduler = string.IsNullOrEmpty(commandExtensionInfo.OutputScheduler) ? string.Empty : $", outputScheduler: {commandExtensionInfo.OutputScheduler}";
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
{
- return $"{fieldName} ??= {RxCmd}{commandType}({commandExtensionInfo.MethodName});";
+ return $"{fieldName} ??= {RxCmd}{commandType}({commandExtensionInfo.MethodName}{outputScheduler});";
}
- return $"{fieldName} ??= {RxCmd}{commandType}({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});";
+ return $"{fieldName} ??= {RxCmd}{commandType}({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)}{outputScheduler});";
}
static string GenerateInOutCommand(CommandInfo commandExtensionInfo, string fieldName, string outputType, string inputType)
{
var commandType = commandExtensionInfo.IsObservable ? CreateO : commandExtensionInfo.IsTask ? CreateT : Create;
+ var outputScheduler = string.IsNullOrEmpty(commandExtensionInfo.OutputScheduler) ? string.Empty : $", outputScheduler: {commandExtensionInfo.OutputScheduler}";
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
{
- return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName});";
+ return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName}{outputScheduler});";
}
- return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});";
+ return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)}{outputScheduler});";
}
static string GenerateInCommand(CommandInfo commandExtensionInfo, string fieldName, string inputType)
{
var commandType = commandExtensionInfo.IsTask ? CreateT : Create;
+ var outputScheduler = string.IsNullOrEmpty(commandExtensionInfo.OutputScheduler) ? string.Empty : $", outputScheduler: {commandExtensionInfo.OutputScheduler}";
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
{
- return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName});";
+ return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName}{outputScheduler});";
}
- return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});";
+ return $"{fieldName} ??= {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)}{outputScheduler});";
}
}
@@ -283,6 +294,93 @@ private static void TryGetCanExecuteExpressionType(
return;
}
+ private static void TryGetOutputScheduler(
+ IMethodSymbol methodSymbol,
+ AttributeData attributeData,
+ out string? outputScheduler)
+ {
+ if (!attributeData.TryGetNamedArgument(OutputScheduler, out string? scheduler))
+ {
+ outputScheduler = null;
+ return;
+ }
+
+ if (scheduler is null)
+ {
+ outputScheduler = null;
+ return;
+ }
+
+ if (scheduler == MainThreadScheduler || scheduler == TaskpoolScheduler)
+ {
+ outputScheduler = scheduler;
+ return;
+ }
+
+ var outputSchedulerSymbols = methodSymbol.ContainingType!.GetAllMembers(scheduler).ToImmutableArray();
+ if (outputSchedulerSymbols.IsEmpty)
+ {
+ outputScheduler = null;
+ return;
+ }
+
+ if (outputSchedulerSymbols.Length > 1)
+ {
+ outputScheduler = null;
+ return;
+ }
+
+ if (TryGetOutputSchedulerFromSymbol(outputSchedulerSymbols[0], out outputScheduler))
+ {
+ return;
+ }
+
+ outputScheduler = null;
+ }
+
+ private static bool TryGetOutputSchedulerFromSymbol(
+ ISymbol outputSchedulerSymbol,
+ [NotNullWhen(true)] out string? outputScheduler)
+ {
+ if (outputSchedulerSymbol is IFieldSymbol outputSchedulerFieldSymbol)
+ {
+ // The property type must always be a bool
+ if (!outputSchedulerFieldSymbol.Type.IsIShedulerType())
+ {
+ goto Failure;
+ }
+
+ outputScheduler = outputSchedulerFieldSymbol.Name;
+ return true;
+ }
+ else if (outputSchedulerSymbol is IPropertySymbol { GetMethod: not null } outputSchedulerPropertySymbol)
+ {
+ // The property type must always be a bool
+ if (!outputSchedulerPropertySymbol.Type.IsIShedulerType())
+ {
+ goto Failure;
+ }
+
+ outputScheduler = outputSchedulerPropertySymbol.Name;
+ return true;
+ }
+ else if (outputSchedulerSymbol is IMethodSymbol outputSchedulerMethodSymbol)
+ {
+ // The return type must always be a bool
+ if (!outputSchedulerMethodSymbol.ReturnType.IsIShedulerType())
+ {
+ goto Failure;
+ }
+
+ outputScheduler = outputSchedulerMethodSymbol.Name;
+ return true;
+ }
+
+ Failure:
+ outputScheduler = null;
+ return false;
+ }
+
///
/// Gets the expression type for the can execute logic, if possible.
///