이전 문서에서는 가장 일반적인 이벤트 패턴에 대해 설명했습니다. .NET Core에는 보다 편안한 패턴이 있습니다. 이 버전에서 EventHandler<TEventArgs>
정의에는 더 이상 TEventArgs
System.EventArgs
파생된 클래스여야 하는 제약 조건이 없습니다.
이렇게 하면 유연성이 향상되고 이전 버전과 호환됩니다. 유연성부터 시작해 보겠습니다.
System.EventArgs의 구현에서는 System.Object에 정의된 MemberwiseClone() 메서드를 사용하여 객체의 얕은 복사본을 만듭니다. 이 메서드는 EventArgs
파생된 클래스에 대한 기능을 구현하기 위해 리플렉션을 사용해야 합니다. 이 기능은 특정 파생 클래스에서 더 쉽게 만들 수 있습니다. 즉, System.EventArgs에서 파생하는 것은 디자인을 제한하는 제약 조건이지만 추가적인 이점을 제공하지는 않습니다. 사실, FileFoundArgs
및 SearchDirectoryArgs
의 정의를 변경하여 EventArgs
에서 파생되지 않도록 할 수 있습니다. 프로그램은 정확히 동일하게 작동합니다.
SearchDirectoryArgs
을 구조체로 변경하려면 한 가지 더 수정해야 합니다.
internal struct SearchDirectoryArgs
{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }
internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs) : this()
{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}
추가 변경 내용은 모든 필드를 초기화하는 생성자를 입력하기 전에 매개 변수가 없는 생성자를 호출하는 것입니다. 이 추가가 없으면 C# 규칙은 할당되기 전에 속성에 액세스하고 있다고 보고합니다.
클래스(참조 형식)에서 구조체(값 형식)로 FileFoundArgs
변경해서는 안 됩니다. 취소를 처리하기 위한 프로토콜을 사용하려면 참조로 이벤트 인수를 전달해야 합니다. 동일한 변경을 수행한 경우 파일 검색 클래스는 이벤트 구독자가 변경한 내용을 관찰할 수 없습니다. 구조체의 새 복사본은 각 구독자에 대해 사용되며, 해당 복사본은 파일 검색 개체에서 볼 수 있는 복사본과는 다른 복사본입니다.
다음으로, 이 변경 내용이 이전 버전과 호환되는 방식을 살펴보겠습니다. 제약 조건을 제거해도 기존 코드에는 영향을 주지 않습니다. 기존 이벤트 인수 형식은 여전히 System.EventArgs
파생합니다. 이전 버전과의 호환성이 그들이 System.EventArgs
에서 파생하는 주요 이유 중 하나입니다. 기존 이벤트 구독자는 클래식 패턴을 따르는 이벤트의 구독자입니다.
비슷한 논리에 따라 이제 만든 이벤트 인수 형식에는 기존 코드베이스에 구독자가 없습니다.
System.EventArgs
파생되지 않는 새 이벤트 형식은 해당 코드베이스를 중단하지 않습니다.
비동기 구독자를 사용하는 이벤트
당신이 배워야 할 마지막 패턴 하나가 있습니다: 비동기 코드를 호출하는 이벤트 구독자를 올바르게 작성하는 방법입니다. 이 문제는 비동기 및 대기에 관한 기사에서 설명되어 있습니다. 비동기 메서드는 void 반환 형식을 가질 수 있지만 권장되지 않습니다. 이벤트 구독자 코드가 비동기 메서드를 호출하는 경우 async void
메서드를 만들 수밖에 없습니다. 이벤트 처리기 서명이 그것을 요구합니다.
이 반대되는 지침을 조정해야 합니다. 어떻게 든 안전한 async void
메서드를 만들어야 합니다. 구현해야 하는 패턴의 기본 사항은 다음 코드에 나와 있습니다.
worker.StartWorking += async (sender, eventArgs) =>
{
try
{
await DoWorkAsync();
}
catch (Exception e)
{
//Some form of logging.
Console.WriteLine($"Async task failure: {e.ToString()}");
// Consider gracefully, and quickly exiting.
}
};
먼저 처리기가 비동기 처리기로 표시됩니다. 이벤트 처리기 대리자 형식에 할당되므로 void 반환 형식이 있습니다. 즉, 핸들러에 표시된 패턴을 따라야 하며, 비동기 핸들러의 컨텍스트에서 예외가 발생하지 않도록 해야 합니다. 작업을 반환하지 않으므로 오류 상태를 입력하여 오류를 보고할 수 있는 작업이 없습니다. 메서드가 비동기이므로 메서드에서 예외를 throw할 수 없습니다. (호출 메서드는 async
때문에 실행을 계속합니다.) 실제 런타임 동작은 다른 환경에 대해 다르게 정의됩니다. 스레드 또는 스레드를 소유하는 프로세스를 종료하거나 프로세스를 확정되지 않은 상태로 둘 수 있습니다. 이러한 모든 잠재적인 결과는 매우 바람직하지 않습니다.
사용자 고유의 try 블록에서 비동기 태스크에 대한 await
식을 래핑해야 합니다. 오류가 발생한 작업을 발생시키는 경우 오류를 기록할 수 있습니다. 애플리케이션이 복구할 수 없는 오류인 경우 프로그램을 신속하고 정상적으로 종료할 수 있습니다.
이 문서에서는 .NET 이벤트 패턴의 주요 업데이트를 설명했습니다. 작업하는 라이브러리에 이전 버전의 많은 예제가 표시될 수 있습니다. 그러나 최신 패턴도 이해해야 합니다. Program.cs 샘플의 완성된 코드를 볼 수 있습니다.
이 시리즈의 다음 문서는 디자인에서 delegates
사용과 events
구분하는 데 도움이 됩니다. 유사한 개념이며, 이 문서는 프로그램에 가장 적합한 결정을 내리는 데 도움이 됩니다.
.NET