이 RelayCommand 형식은 주석이 추가된 메서드에 대한 릴레이 명령 속성을 생성할 수 있는 특성입니다. 그 목적은 viewmodel에서 프라이빗 메서드를 래핑하는 명령을 정의하는 데 필요한 상용구가 완전히 제거되는 것입니다.
Note
작동하려면 주석이 추가된 메서드가 partial 클래스에 있어야 합니다. 형식이 중첩 형식인 경우 선언 구문 트리에 있는 모든 형식도 partial로 지정되어 있어야 합니다. 이렇게 하지 않으면 생성기가 요청된 명령을 사용하여 해당 형식의 다른 부분 선언을 생성할 수 없으므로 컴파일 오류가 발생합니다.
플랫폼 API:
RelayCommand, ,ICommandIRelayCommand,IRelayCommand<T>,IAsyncRelayCommandIAsyncRelayCommand<T>,TaskCancellationToken
작동 방식
다음과 RelayCommand 같이 부분 형식의 메서드에 주석을 추가하는 데 이 특성을 사용할 수 있습니다.
[RelayCommand]
private void GreetUser()
{
Console.WriteLine("Hello!");
}
그리고 다음과 같은 명령을 생성합니다.
private RelayCommand? greetUserCommand;
public IRelayCommand GreetUserCommand => greetUserCommand ??= new RelayCommand(GreetUser);
Note
생성된 명령의 이름은 메서드 이름을 기반으로 만들어집니다. 생성기는 메서드 이름을 사용하고 끝에 "Command"를 추가하며, 있는 경우 "On" 접두사를 제거합니다. 또한 비동기 메서드의 경우 "명령"을 추가하기 전에 "Async" 접미사도 제거됩니다.
명령 매개 변수
이 특성은 [RelayCommand] 매개 변수가 있는 메서드에 대한 명령 만들기를 지원합니다. 이 경우 생성된 명령 IRelayCommand<T> 이 동일한 형식의 매개 변수를 수락하는 대신 자동으로 변경됩니다.
[RelayCommand]
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
이렇게 하면 다음과 같은 코드가 생성됩니다.
private RelayCommand<User>? greetUserCommand;
public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);
결과 명령은 인수의 형식을 해당 형식 인수로 자동으로 사용합니다.
비동기 명령
또한 [RelayCommand] 명령은 IAsyncRelayCommand 및 IAsyncRelayCommand<T> 인터페이스를 통해 비동기 메서드 래핑도 지원합니다. 메서드가 형식을 반환 Task 할 때마다 자동으로 처리됩니다. 예를 들면 다음과 같습니다.
[RelayCommand]
private async Task GreetUserAsync()
{
User user = await userService.GetCurrentUserAsync();
Console.WriteLine($"Hello {user.Name}!");
}
그러면 다음 코드가 생성됩니다.
private AsyncRelayCommand? greetUserCommand;
public IAsyncRelayCommand GreetUserCommand => greetUserCommand ??= new AsyncRelayCommand(GreetUserAsync);
메서드가 매개 변수를 사용하는 경우 결과 명령도 제네릭이 됩니다.
메서드에 CancellationToken가 있는 경우는 특별한 경우인데, 이는 취소를 활성화할 수 있도록 해당 CancellationToken가 명령에 전달되기 때문입니다. 즉, 다음과 같은 메서드입니다.
[RelayCommand]
private async Task GreetUserAsync(CancellationToken token)
{
try
{
User user = await userService.GetCurrentUserAsync(token);
Console.WriteLine($"Hello {user.Name}!");
}
catch (OperationCanceledException)
{
}
}
생성된 명령이 래핑된 메서드에 토큰을 전달하게 됩니다. 이를 통해 소비자는 해당 토큰을 신호로 호출 IAsyncRelayCommand.Cancel 하고 보류 중인 작업을 올바르게 중지할 수 있습니다.
명령 사용 및 사용 안 함
명령을 사용하지 않도록 설정한 다음 나중에 해당 상태를 무효화하고 실행할 수 있는지 여부를 다시 확인하도록 하는 것이 유용한 경우가 많습니다. 이를 지원하기 위해 RelayCommand 특성은 CanExecute 속성을 노출하며, 이 속성은 명령을 실행할 수 있는지 여부를 평가하는 데 사용할 대상 속성 또는 메서드를 지정하는 데 사용할 수 있습니다:
[RelayCommand(CanExecute = nameof(CanGreetUser))]
private void GreetUser(User? user)
{
Console.WriteLine($"Hello {user!.Name}!");
}
private bool CanGreetUser(User? user)
{
return user is not null;
}
이렇게 하면 버튼이 처음 UI에 바인딩될 때(예: 버튼에) CanGreetUser가 호출되고, 그 후에는 명령에서 IRelayCommand.NotifyCanExecuteChanged가 호출될 때마다 CanGreetUser가 다시 호출됩니다.
예를 들어 명령을 속성에 바인딩하여 상태를 제어할 수 있습니다.
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private User? selectedUser;
<!-- Note: this example uses traditional XAML binding syntax -->
<Button
Content="Greet user"
Command="{Binding GreetUserCommand}"
CommandParameter="{Binding SelectedUser}"/>
이 예제에서 생성된 SelectedUser 속성은 값이 변경될 때마다 메서드를 호출 GreetUserCommand.NotifyCanExecuteChanged() 합니다. UI에는 Button에 바인딩된 GreetUserCommand 컨트롤이 있으며, 이는 해당 CanExecuteChanged 이벤트가 발생할 때마다 CanExecute 메서드를 다시 호출한다는 의미입니다. 이렇게 하면 래핑된 CanGreetUser 메서드가 평가되며, 그러면 입력 User 인스턴스(UI에서는 SelectedUser 속성에 바인딩됨)가 null인지 여부에 따라 버튼의 새 상태가 반환됩니다. 즉, SelectedUser 변경될 때마다 GreetUserCommand 이 시나리오에서 원하는 동작인 값이 속성에 있는지 여부에 따라 활성화됩니다.
Note
메서드 또는 속성의 반환 값 이 변경된 경우 명령은 자동으로 인식CanExecute. 명령을 무효화하고 연결된 CanExecute 메서드가 다시 평가되어 명령에 바인딩된 컨트롤의 시각적 상태가 업데이트되도록 하려면, 개발자가 IRelayCommand.NotifyCanExecuteChanged를 호출해야 합니다.
동시 실행 처리
명령이 비동기일 때마다 동시 실행을 허용할지 여부를 결정하도록 구성할 수 있습니다.
RelayCommand 특성을 사용할 때 이는 AllowConcurrentExecutions 속성을 통해 설정할 수 있습니다. 기본값은 false실행이 보류될 때까지 명령이 해당 상태를 비활성화됨으로 알리는 것을 의미합니다. 대신 true로 설정하면 개수 제한 없이 동시 호출을 대기열에 추가할 수 있습니다.
명령이 취소 토큰을 수락하는 경우 동시 실행이 요청되면 토큰도 취소됩니다. 주요 차이점은 동시 실행이 허용되는 경우 명령이 활성화된 상태로 유지되며 이전 실행이 실제로 완료될 때까지 기다리지 않고 요청된 새 실행을 시작한다는 것입니다.
비동기 예외 처리
비동기 릴레이 명령에서 예외를 처리하는 방법에는 두 가지가 있습니다.
- Await and rethrow (기본값): 명령이 호출이 완료되기를 기다리면 모든 예외가 자연스럽게 동일한 동기화 컨텍스트에서 다시 throw됩니다. 이는 일반적으로 예외가 발생하면 앱이 그대로 충돌하게 됨을 의미하며, 이러한 동작은 동기식 명령의 동작과도 일관됩니다(동기식 명령에서도 예외가 발생하면 앱이 충돌함).
-
예외를 작업 스케줄러로 전달: 명령이 예외를 작업 스케줄러로 전달하도록 구성된 경우, 발생한 예외는 앱을 충돌시키지 않고 노출된
IAsyncRelayCommand.ExecutionTask을 통해 사용할 수 있으며TaskScheduler.UnobservedTaskException까지 전파됩니다. 이렇게 하면 고급 시나리오(예: UI 구성 요소가 작업에 바인딩되고 작업의 결과에 따라 다른 결과가 표시됨)가 가능하지만 올바르게 사용하는 것이 더 복잡합니다.
기본 동작에는 대기 및 다시 throw 예외 명령이 있습니다. 속성을 통해 구성할 수 있습니다.FlowExceptionsToTaskScheduler
[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task GreetUserAsync(CancellationToken token)
{
User user = await userService.GetCurrentUserAsync(token);
Console.WriteLine($"Hello {user.Name}!");
}
이 경우 try/catch 예외가 더 이상 앱에 충돌하지 않으므로 필요하지 않습니다. 이렇게 하면 관련 없는 다른 예외가 자동으로 다시 throw되지 않으므로 각 개별 시나리오에 접근하고 나머지 코드를 적절하게 구성하는 방법을 신중하게 결정해야 합니다.
비동기 작업에 대한 명령 취소
비동기 명령에 대한 마지막 옵션 중 하나는 생성할 취소 명령을 요청하는 기능입니다. 이것은 작업의 취소를 요청하는 데 사용할 수 있는 비동기 릴레이 명령을 래핑하는 ICommand입니다. 이 명령은 지정된 시간에 사용할 수 있는지 여부를 반영하도록 상태를 자동으로 알릴 것입니다. 예를 들어 연결된 명령이 실행되지 않는 경우 해당 상태도 실행 가능하지 않다고 보고합니다. 다음과 같이 사용할 수 있습니다.
[RelayCommand(IncludeCancelCommand = true)]
private async Task DoWorkAsync(CancellationToken token)
{
// Do some long running work...
}
이렇게 하면 DoWorkCancelCommand 속성도 생성됩니다. 그러면 사용자가 보류 중인 비동기 작업을 쉽게 취소할 수 있도록 다른 UI 구성 요소에 바인딩할 수 있습니다.
사용자 지정 특성 추가
관찰 가능한 속성RelayCommand과 마찬가지로 생성기에는 생성된 속성에 대한 사용자 지정 특성에 대한 지원도 포함됩니다. 이를 활용하려면 특성이 적용된 메서드의 특성 목록에서 [property: ] 대상을 사용하기만 하면 되며, MVVM Toolkit은 해당 특성들을 생성된 명령 속성으로 전달합니다.
예를 들어 다음과 같은 메서드를 고려합니다.
[RelayCommand]
[property: JsonIgnore]
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
이렇게 하면 GreetUserCommand 속성이 생성되며, 여기에 [JsonIgnore] 특성이 적용됩니다. 메서드를 대상으로 하는 특성 목록을 원하는 만큼 사용할 수 있으며 모든 특성이 생성된 속성으로 전달됩니다.
예제
MVVM Toolkit