-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathApp.cs
191 lines (135 loc) · 4.42 KB
/
App.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
namespace SampleApp;
/* The root of our application
*
* The interface has no "I" prefix as it's conceptually no longer an "interface".
*/
public interface JobList
{
ILogger? Logger { get; init; }
Func<CommandConfig, Command> NewCommand { get; init; }
Func<CancellationToken, SimpleJob> NewSimpleJob { get; init; }
Func<CancellationToken, ComplexJob> NewComplexJob { get; init; }
CancellationTokenSource? Cts { get; set; }
Boolean HaveCancellableJobs => Cts is not null;
CancellationToken Ct => GetCts().Token;
IList<IJob> Items { get; set; }
CancellationTokenSource GetCts() => Cts ?? (Cts = new CancellationTokenSource());
Command AddSimpleJobCommand => MakeCommand(() => AddAndRunJob(NewSimpleJob(Ct)));
Command AddComplexJobCommand => MakeCommand(() => AddAndRunJob(NewComplexJob(Ct)));
Command CancelCommand => MakeCommand(() => Cancel(), HaveCancellableJobs);
Command MakeCommand(Action execute, Boolean isEnabled = true)
=> NewCommand(new CommandConfig(execute, isEnabled));
async void AddAndRunJob(IJob job)
{
Items.Add(job);
await job.Run();
await Task.Delay(3000);
Items.Remove(job);
if (Items.Count == 0)
{
Cts = null;
}
}
void Cancel()
{
Cts?.Cancel();
Logger?.Log($"Cancelled at {DateTime.Now}");
Cts = null;
}
}
/* We can still use polymorphism, and this type is indeed an
* interface semantically, hence the "I" in "IJob".
*/
public record JobNestingLevel(Int32 Level);
public interface IJob
{
CancellationToken Ct { get; init; }
JobNestingLevel? NestingLevel { get; init; }
ILogger? Logger { get; init; }
Boolean HasEnded { get; set; }
Exception? Exception { get; set; }
String StatusString => this switch
{
{ Exception: OperationCanceledException } => "cancelled",
{ Exception: Exception e } => $"error: {e.Message}",
{ HasEnded: true } => "completed",
_ => "running"
};
async Task Run()
{
try
{
await RunImpl();
}
catch (Exception ex)
{
Logger?.Log($"Job aborted at {DateTime.Now}");
Exception = ex;
}
finally
{
HasEnded = true;
}
}
Task RunImpl();
}
/* "SimpleJob"s are the first implementation.
*/
public record SimpleJobConfig(TimeSpan Duration, Int32 Steps);
public interface SimpleJob : IJob
{
SimpleJobConfig? Config { get; init; }
Int32 Progress { get; set; }
async Task IJob.RunImpl()
{
var config = Config ?? new SimpleJobConfig(TimeSpan.FromSeconds(3), 50);
var n = config.Steps;
var ms = config.Duration.TotalMilliseconds;
for (var i = 0; i < n; ++i)
{
Ct.ThrowIfCancellationRequested();
await Task.Delay((Int32)(ms / n));
Progress = 100 * i / n;
}
Progress = 100;
}
}
/* "ComplexJob"s are the second implementation and use nested "SimpleJob"s.
*/
public interface ComplexJob : IJob
{
Func<JobNestingLevel, SimpleJob> CreateSimpleJob { get; init; }
IList<IJob> SubJobs { get; set; }
async Task IJob.RunImpl()
{
for (var i = 0; i < 3; ++i)
{
var subJob = CreateSimpleJob(new JobNestingLevel((NestingLevel?.Level ?? 0) + 1));
SubJobs.Add(subJob);
await subJob.Run();
}
await Task.Delay(1000, Ct);
}
}
/* Commands are usually not used anywhere but in the XAML world, but they can
* be, and we want to one app to be nicely bindable to all UI frameworks.
*
* It also could as well be a class here, but that I could no longer claim that
* this sample app only uses interfaces and records.
*/
public record CommandConfig(Action Execute, Boolean CanExecute = true);
public interface Command : System.Windows.Input.ICommand
{
CommandConfig Config { get; init; }
event EventHandler? System.Windows.Input.ICommand.CanExecuteChanged { add { } remove { } }
Boolean IsDisabled => !Config.CanExecute;
Boolean System.Windows.Input.ICommand.CanExecute(Object? parameter) => Config.CanExecute;
void System.Windows.Input.ICommand.Execute(Object? parameter) => Config.Execute();
}
/* This is to demonstrate how a concrete implementation can be injected
* from outside this application.
*/
public interface ILogger
{
void Log(String message);
}