Skip to content

Commit 0c05308

Browse files
committed
improve async task command handling
1 parent 3749a8f commit 0c05308

File tree

3 files changed

+64
-8
lines changed

3 files changed

+64
-8
lines changed

SimpleViewModel/BaseClasses/BaseViewModel.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.ComponentModel;
22
using System.Runtime.CompilerServices;
3+
using System.Runtime.ExceptionServices;
34

45
namespace SimpleViewModel.BaseClasses;
56

@@ -9,6 +10,8 @@ namespace SimpleViewModel.BaseClasses;
910
/// </summary>
1011
public class BaseViewModel : INotifyPropertyChanged
1112
{
13+
private readonly SynchronizationContext? _syncContext = SynchronizationContext.Current;
14+
1215
/// <inheritdoc/>
1316
public event PropertyChangedEventHandler? PropertyChanged;
1417

@@ -21,9 +24,11 @@ public class BaseViewModel : INotifyPropertyChanged
2124
/// <param name="propertyName">The name of the property. This is optional and is automatically provided by the compiler.</param>
2225
protected void SetProperty<T>(ref T reference, T value, [CallerMemberName] string propertyName = "")
2326
{
24-
if (Equals(reference, value)) return;
27+
if (EqualityComparer<T>.Default.Equals(reference, value))
28+
return;
29+
2530
reference = value;
26-
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
31+
RaisePropertyChanged(propertyName);
2732
}
2833

2934
/// <summary>
@@ -32,4 +37,27 @@ protected void SetProperty<T>(ref T reference, T value, [CallerMemberName] strin
3237
/// <param name="propertyName">The name of the property. This is optional and is automatically provided by the compiler.</param>
3338
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
3439
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
40+
41+
private void RaisePropertyChanged(string propertyName)
42+
{
43+
var handler = PropertyChanged;
44+
if (handler is null)
45+
return;
46+
47+
if (_syncContext != null)
48+
{
49+
_syncContext.Post(_ => handler(this, new(propertyName)), null);
50+
}
51+
else
52+
{
53+
handler(this, new(propertyName));
54+
}
55+
}
56+
57+
/// <summary>
58+
/// Called when a generated command throws.
59+
/// Default behavior preserves stack and crashes (correct for debugging).
60+
/// Override in derived VMs for logging or UI reporting.
61+
/// </summary>
62+
public virtual void OnCommandException(Exception ex) => ExceptionDispatchInfo.Capture(ex).Throw();
3563
}

SimpleViewModel/SimpleViewModel.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1111
<PackageId>SimpleViewModel</PackageId>
12-
<Version>0.9.7.4</Version>
12+
<Version>0.9.8.0</Version>
1313
<Authors>Derek Gooding</Authors>
1414
<Company>Derek Gooding</Company>
1515
<Description>

ViewModelGenerator/ViewModelGenerator.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public partial class {className} : BaseViewModel
100100
var paramType = hasParameter ? method.Parameters[0].Type.ToDisplayString() : null;
101101

102102
// Generate command class per method
103+
var isAsync = IsAsyncTask(method);
103104
var commandBuilder = new StringBuilder(
104105
$@"// <auto-generated />
105106
#nullable enable
@@ -116,20 +117,33 @@ public sealed class {commandClassName} : BaseCommand
116117
this.vm = vm;
117118
}}
118119
119-
public override void Execute(object? parameter)
120+
public override {(isAsync ? "async void" : "void")} Execute(object? parameter)
120121
{{
121122
");
123+
commandBuilder.AppendLine(" try");
124+
commandBuilder.AppendLine(" {");
125+
122126
if (acceptParameter && hasParameter)
123127
{
124-
// Pass parameter to method
125-
commandBuilder.AppendLine($" vm.{method.Name}(({paramType})parameter!);");
128+
if (isAsync)
129+
commandBuilder.AppendLine($" await vm.{method.Name}(({paramType})parameter!);");
130+
else
131+
commandBuilder.AppendLine($" vm.{method.Name}(({paramType})parameter!);");
126132
}
127133
else
128134
{
129-
// Call parameterless method
130-
commandBuilder.AppendLine($" vm.{method.Name}();");
135+
if (isAsync)
136+
commandBuilder.AppendLine($" await vm.{method.Name}();");
137+
else
138+
commandBuilder.AppendLine($" vm.{method.Name}();");
131139
}
132140

141+
commandBuilder.AppendLine(" }");
142+
commandBuilder.AppendLine(" catch (Exception ex)");
143+
commandBuilder.AppendLine(" {");
144+
commandBuilder.AppendLine(" vm.OnCommandException(ex);");
145+
commandBuilder.AppendLine(" }");
146+
133147
commandBuilder.AppendLine(" }");
134148
var canExecute = GetCanExecuteMethodName(method);
135149
if (!string.IsNullOrWhiteSpace(canExecute))
@@ -189,4 +203,18 @@ private static string ToPascal(string name)
189203
}
190204
return null;
191205
}
206+
207+
private static bool IsAsyncTask(IMethodSymbol method)
208+
{
209+
var returnType = method.ReturnType;
210+
211+
if (returnType is INamedTypeSymbol named)
212+
{
213+
if (named.Name == "Task" &&
214+
named.ContainingNamespace.ToDisplayString() == "System.Threading.Tasks")
215+
return true;
216+
}
217+
218+
return false;
219+
}
192220
}

0 commit comments

Comments
 (0)