.NET의 이벤트 서명 - 강력한 유형의 '발신자'를 사용하시겠습니까?
저는 제가 제안하는 것이 .NET 지침을 따르지 않는다는 것을 충분히 알고 있습니다. 따라서 이러한 이유만으로 볼 때는 좋지 않은 생각일 수도 있습니다.그러나, 저는 두 가지 가능한 관점에서 이를 고려하고 싶습니다.
100% 내부 목적인 나만의 개발 작업에 사용하는 것을 고려해야 할까요?
프레임워크 설계자가 변경 또는 업데이트를 고려할 수 있는 개념입니까?
현재 .NET 디자인 패턴인 'object'로 입력하는 대신 강력한 type의 'sender'를 활용한 이벤트 시그니처를 사용하려고 합니다.즉, 다음과 같은 표준 이벤트 서명을 사용하는 대신 다음과 같이 표시됩니다.
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
다음과 같이 강력한 형식의 '발신자' 매개 변수를 사용하는 이벤트 서명을 사용할 것을 고려 중입니다.
먼저 "강력한 유형의 이벤트 처리기"를 정의합니다.
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
>와 TSENDER, TEVENTArgs>, TEVENTArgs를 으로써 합니다.StrongTypedEventHandler우리는 TEVENTArgs가 다음에서 유래한다고 시행합니다.System.EventArgs.
다음으로 게시 클래스에서 StrongTypeEventHandler를 다음과 같이 사용할 수 있습니다.
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
protected void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs(...));
}
}
}
위와 같은 방식으로 가입자는 캐스팅이 필요 없는 강력한 유형의 이벤트 핸들러를 사용할 수 있습니다.
class Subscriber
{
void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
if (sender.Name == "John Smith")
{
// ...
}
}
}
이것이 표준 .NET 이벤트 처리 패턴과 맞지 않는다는 것을 잘 알고 있습니다. 하지만 원하는 경우 가입자가 기존 이벤트 처리 서명을 사용할 수 있습니다.
class Subscriber
{
void SomeEventHandler(object sender, PublisherEventArgs e)
{
if (((Publisher)sender).Name == "John Smith")
{
// ...
}
}
}
즉, 이벤트 핸들러가 서로 다른(또는 알 수 없는) 개체 유형의 이벤트를 구독해야 하는 경우 핸들러는 잠재적인 보낸 사람 개체의 전체 범위를 처리하기 위해 'sender' 매개 변수를 'object'로 입력할 수 있습니다.
관례를 깨는 것 외에는 (내가 가볍게 여기지 않는 것, 나를 믿어라) 이것에 대한 어떤 단점도 생각할 수 없습니다.
여기에는 몇 가지 CLS 규정 준수 문제가 있을 수 있습니다.이는 Visual Basic .NET 2008에서 100% 미세하게 실행되지만(테스트한 바에 따르면) Visual Basic .NET에서 2005까지의 이전 버전에는 대리 공분산 및 반분산이 없다고 생각합니다.[편집: 이후 테스트를 해보니 확인됨: VB.NET 2005 이하에서는 처리할 수 없지만 VB.NET 2008은 100% 정상입니다. 아래의 "편집 #2"를 참조하십시오.]다른 .NET 언어에도 이 문제가 있을 수 있습니다. 확실하지 않습니다.
그러나 C# 또는 Visual Basic .NET 이외의 언어로 개발되는 것은 아니라고 생각하며, .NET Framework 3.0 이상의 언어는 C# 및 VB.NET으로 제한해도 상관없습니다.(솔직히 이 시점에서 2.0으로 돌아갈 것이라고는 상상할 수 없었습니다.)
다른 사람들이 이것에 대한 문제를 생각해 낼 수 있습니까?아니면 이것이 단순히 관습과 너무 많이 어긋나서 사람들을 속이 뒤집히게 만드는 것일까요?
다음은 제가 찾은 몇 가지 관련 링크입니다.
C# 간단한 이벤트 상승 - "발신자" 대 사용자 지정 이벤트 인수 사용 [StackOverflow 2009]
.net의 이벤트 서명 패턴 [StackOverflow 2008]
나는 이것에 대한 누구의 의견에도 관심이 있습니다.
잘 부탁드립니다.
마이크
편집 #1: Tommy Carlier의 게시물에 대한 답변입니다.
다음은 강력한 유형의 이벤트 처리기와 'object sender' 매개 변수를 사용하는 현재 표준 이벤트 처리기가 모두 이 접근 방식과 공존할 수 있음을 보여주는 전체 작업 예제입니다.코드에 복사하여 붙여넣고 실행할 수 있습니다.
namespace csScrap.GenericEventHandling
{
class PublisherEventArgs : EventArgs
{
// ...
}
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
public void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs());
}
}
}
class StrongTypedSubscriber
{
public void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.");
}
}
class TraditionalSubscriber
{
public void SomeEventHandler(object sender, PublisherEventArgs e)
{
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.");
}
}
class Tester
{
public static void Main()
{
Publisher publisher = new Publisher();
StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber();
TraditionalSubscriber traditionalSubscriber = new TraditionalSubscriber();
publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler;
publisher.SomeEvent += traditionalSubscriber.SomeEventHandler;
publisher.OnSomeEvent();
}
}
}
편집 #2:이것은 공분산과 반변성에 관한 Andrew Hare의 진술과 그것이 여기에 어떻게 적용되는지에 대한 답변입니다.C# 언어의 대표자들은 너무 오랫동안 공분산과 반변성을 가지고 있어서 그저 "본질적"으로 느껴지지만 그렇지 않습니다.CLR에서 사용할 수 있는 기능일 수도 있지만 Visual Basic .NET은 .NET Framework 3.0(VB.NET 2008)이 될 때까지 대리자를 위한 공분산 및 반분산 기능을 제공하지 않았습니다.그 결과 Visual Basic.NET for.NET 2.0 이하에서는 이 접근 방식을 활용할 수 없습니다.
예를 들어, 위의 예는 다음과 같이 VB.NET으로 변환할 수 있습니다.
Namespace GenericEventHandling
Class PublisherEventArgs
Inherits EventArgs
' ...
' ...
End Class
<SerializableAttribute()> _
Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _
(ByVal sender As TSender, ByVal e As TEventArgs)
Class Publisher
Public Event SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs)
Public Sub OnSomeEvent()
RaiseEvent SomeEvent(Me, New PublisherEventArgs)
End Sub
End Class
Class StrongTypedSubscriber
Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs)
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.")
End Sub
End Class
Class TraditionalSubscriber
Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs)
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.")
End Sub
End Class
Class Tester
Public Shared Sub Main()
Dim publisher As Publisher = New Publisher
Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber
Dim traditionalSubscriber As TraditionalSubscriber = New TraditionalSubscriber
AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandler
AddHandler publisher.SomeEvent, AddressOf traditionalSubscriber.SomeEventHandler
publisher.OnSomeEvent()
End Sub
End Class
End Namespace
VB.NET 2008은 100% 정상적으로 실행할 수 있습니다.하지만 만약을 위해 VB.NET 2005에서 테스트를 해봤지만 컴파일이 되지 않습니다.
메서드 'Public Sub SomeEventHandler(개체로 보내기, eAs vbGenericEventHandling.일반 이벤트 처리.PublisherEventArgs)'이(가) 대리자 'Delegate Sub StrongTypeEventHandler(Of TSender, TEVENTArgs As System)'와 동일한 서명을 가지고 있지 않습니다.EventArgs)(Publisher로 전송, ePublisherEventArgs로 전송)'
기본적으로 VB.NET 버전 2005 이하에서는 대리인이 불변합니다.사실 몇 년 전에 이런 생각을 했었는데 VB.NET이 이 문제를 해결할 능력이 없어서 신경이 쓰였어요하지만 저는 이제 C#으로 완전히 옮겨갔고, VB.NET은 이제 이 일을 처리할 수 있습니다.
편집: 업데이트 #3
네, 저는 이것을 꽤 오랫동안 성공적으로 사용해 왔습니다.그것은 정말 좋은 시스템입니다.저는 제 "StrongTypeEventHandler"를 다음과 같이 정의된 "GenericEventHandler"로 명명하기로 결정했습니다.
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
이 이름 변경 외에는 위에서 논의한 대로 정확히 구현했습니다.
다음과 같은 FxCop 규칙 CA1009를 적용합니다.
"관습적으로 .NET 이벤트에는 이벤트 보낸 사람과 이벤트 데이터를 지정하는 두 가지 매개 변수가 있습니다.이벤트 처리기 서명은 void MyEventHandler(개체 보낸 사람, EventArges) 형식을 따라야 합니다.'sender' 매개 변수는 항상 시스템 유형입니다.개체, 보다 구체적인 유형을 사용할 수 있는 경우에도 마찬가지입니다.'e' 매개 변수는 항상 시스템 유형입니다.이벤트 인수.이벤트 데이터를 제공하지 않는 이벤트는 시스템을 사용해야 합니다.EventHandler 대리자 유형입니다.이벤트 핸들러는 void를 반환하여 각 이벤트를 여러 대상 메서드로 보낼 수 있습니다.대상이 반환한 값은 첫 번째 호출 후에 손실됩니다."
물론, 우리는 이 모든 것을 알고 있고, 어쨌든 규칙을 어기고 있습니다. (모든 이벤트 처리기는 원하는 경우 서명에 표준 'object Sender'를 사용할 수 있습니다. 이는 중단 없는 변경 사항입니다.)
래의서사용의 은.SuppressMessageAttribute요령을 터득합니다.
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
저는 이 접근법이 미래의 어느 시점에서 표준이 되기를 바랍니다.정말 잘 작동합니다.
여러분의 의견에 감사드립니다, 정말 감사합니다...
마이크
MSDN에 유사한 사례가 있기 때문에 Microsoft가 이를 인식한 것으로 보입니다.
당신이 제안하는 것은 실제로 많은 의미가 있습니다. 그리고 저는 이것이 단순히 그것이 원래 제네릭 이전에 디자인되었기 때문에 그런 것들 중 하나인지 아니면 이것에 대한 진짜 이유가 있는지 궁금합니다.
WinRT)에는 Windows Runtime(WinRT)이 되어 있습니다.TypedEventHandler<TSender, TResult>대리인, 정확히 당신의 일을 합니다.StrongTypedEventHandler<TSender, TResult>그습 다니렇에 것처럼 보입니다.TResult유형 매개변수:
public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
TResult args);
MSDN 설명서는 여기에 있습니다.
저는 다음과 같은 진술에 이의를 제기합니다.
- Visual Basic .NET에서 2005까지의 이전 버전에는 대리 공분산과 반변성이 없다고 생각합니다.
- 저는 이것이 신성모독에 가깝다는 것을 완전히 알고 있습니다.
우선 여기서 수행한 작업은 공분산 또는 반분산과 관련이 없습니다. (편집:이전 문장이 잘못되었습니다. 자세한 내용은 딜러의 공분산 및 반변성을 참조하십시오.)이 솔루션은 모든 CLR 버전 2.0 이상에서 잘 작동합니다(분명히 CLR 1.0 애플리케이션에서는 제네릭을 사용하므로 작동하지 않습니다).
두 번째로, 저는 당신의 아이디어가 "블라피미"에 가깝다는 것에 강하게 동의하지 않습니다. 왜냐하면 이것은 훌륭한 아이디어이기 때문입니다.
저는 새로운 WinRT에서 이 문제가 어떻게 처리되었는지 그리고 여기에 있는 다른 의견들을 바탕으로 살짝 살펴보았고, 마침내 다음과 같이 하기로 결정했습니다.
[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
TSender sender,
TEventArgs e
) where TEventArgs : EventArgs;
WinRT에서 TypedEventHandler라는 이름을 사용하는 것을 고려할 때 이것이 가장 좋은 방법인 것 같습니다.
저는 그것이 좋은 생각이라고 생각합니다. MS는 ArrayList에서 일반 기반 목록으로 전환할 때처럼 이를 개선하는 데 투자할 시간이나 관심이 없을 수 있습니다.
제가 알기로는 "발신자" 필드는 항상 이벤트 구독을 보유하고 있는 개체를 가리키도록 되어 있습니다.만약 내가 서더를 가지고 있다면, 이벤트가 필요할 때 구독을 취소하기에 충분한 정보를 보유한 필드도 있을 것입니다(*). (예를 들어, '수집 변경' 이벤트를 구독하는 변경 로거를 생각해 보십시오. 이 변경 로거는 두 부분으로 구성되어 있으며, 그 중 하나는 실제 작업을 수행하고 실제 데이터를 보유하고 있습니다.다른 하나는 공용 인터페이스 래퍼를 제공합니다. 주요 부분은 래퍼 부분에 대한 약한 참조를 보유할 수 있습니다.래퍼 부분이 가비지 수집되면 수집되는 데이터에 더 이상 관심이 있는 사람이 없으므로 변경 로거는 수신되는 모든 이벤트에서 등록을 취소해야 합니다.
개체가 다른 개체를 대신하여 이벤트를 보낼 수 있기 때문에 개체 유형의 "발신자" 필드와 EventArgs에서 파생된 필드에 작업할 개체에 대한 참조가 포함되어 있는 경우 잠재적인 유용성을 알 수 있습니다.그러나 "발신자" 필드의 유용성은 개체가 알 수 없는 발신자로부터 수신을 취소할 수 있는 깨끗한 방법이 없다는 사실에 의해 제한될 수 있습니다.
(*) 사실, 불리언을 반환하는 함수에 대한 멀티캐스트 대리자 유형을 갖는 것이 더 깔끔할 것입니다. 이러한 대리자가 호출한 함수가 True를 반환하면 대리자가 패치되어 해당 개체를 제거할 것입니다.이는 딜러가 더 이상 진정으로 불변하지는 않지만 스레드 안전한 방식으로 이러한 변경을 수행할 수 있음을 의미합니다(예: 객체 참조를 제외하고 멀티캐스트 대리자 코드가 내장된 null 객체 참조를 무시하도록 함).이 시나리오에서는 이벤트의 출처에 관계없이 삭제된 개체에 대한 게시 및 이벤트를 매우 깨끗하게 처리할 수 있습니다.
보낸 사람을 객체 유형으로 만드는 유일한 이유로 신성 모독을 돌아보면(마이크로소프트의 실수 IMHO인 VB 2005 코드의 위반 문제를 생략하는 경우), 적어도 EventArgs 유형에 두 번째 인수를 못박는 이론적 동기를 제시할 수 있습니다.더 나아가, 이 특별한 경우에 마이크로소프트의 지침과 관례를 준수해야 할 타당한 이유가 있습니까?
이벤트 핸들러 내부에 전달하고자 하는 다른 데이터에 대해 다른 EventArgs 래퍼를 개발해야 하는 것이 이상하게 보이는데, 왜 그 데이터를 바로 전달할 수 없습니까?코드의 다음 섹션을 고려합니다.
[예 1]
public delegate void ConnectionEventHandler(Server sender, Connection connection);
public partial class Server
{
protected virtual void OnClientConnected(Connection connection)
{
if (ClientConnected != null) ClientConnected(this, connection);
}
public event ConnectionEventHandler ClientConnected;
}
[예 2]
public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);
public class ConnectionEventArgs : EventArgs
{
public Connection Connection { get; private set; }
public ConnectionEventArgs(Connection connection)
{
this.Connection = connection;
}
}
public partial class Server
{
protected virtual void OnClientConnected(Connection connection)
{
if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
}
public event ConnectionEventHandler ClientConnected;
}
현재 상황(발신자가 개체임)에서는 메서드를 여러 이벤트에 쉽게 연결할 수 있습니다.
button.Click += ClickHandler;
label.Click += ClickHandler;
void ClickHandler(object sender, EventArgs e) { ... }
발신인이 일반적인 경우 클릭 이벤트의 대상은 단추 또는 레이블 유형이 아니라 컨트롤 유형입니다(이벤트가 컨트롤에 정의되어 있기 때문).따라서 Button 클래스의 일부 이벤트에는 Control 유형의 대상이 있고 다른 이벤트에는 다른 대상 유형이 있습니다.
저는 당신이 하고 싶은 일에 아무런 문제가 없다고 생각합니다.대부분의 경우, 제 생각에는object sender매개 변수는 2.0 이전 코드를 계속 지원하기 위해 남아 있습니다.
공용 API에 대해 이를 변경하려면 자신만의 기본 EvenArgs 클래스를 만드는 것을 고려해야 합니다.이와 같은 것:
public class DataEventArgs<TSender, TData> : EventArgs
{
private readonly TSender sender, TData data;
public DataEventArgs(TSender sender, TData data)
{
this.sender = sender;
this.data = data;
}
public TSender Sender { get { return sender; } }
public TData Data { get { return data; } }
}
그런 다음 이벤트를 다음과 같이 선언할 수 있습니다.
public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;
이러한 방법은 다음과 같습니다.
private void HandleSomething(object sender, EventArgs e)
계속 구독할 수 있습니다.
편집
그 마지막 대사는 제가 생각하게 했습니다.런타임에는 매개 변수를 다운캐스팅하는 데 문제가 없기 때문에 외부 기능을 중단하지 않고 실제로 제안한 내용을 구현할 수 있어야 합니다.나는 여전히 그 쪽으로 기울 것입니다.DataEventArgs개인적으로 해결하다전송자가 첫 번째 매개 변수에 저장되고 이벤트 인수의 속성으로 저장되기 때문에 중복된다는 것을 알고 있더라도 그렇게 할 것입니다.
지속적으로 사용함으로써 얻을 수 있는 한 가지 이점DataEventArgs즉, EventArgs가 원래 발신인을 유지하는 동안 발신인을 변경하여(마지막 발신인을 나타냄) 이벤트를 체인으로 연결할 수 있습니다.
그것을 한번 해 보시지요.비컴포넌트 기반 코드의 경우 이벤트 서명을 단순화하는 경우가 많습니다.
public event Action<MyEventType> EventName
어디에MyEventType에서 상속되지 않음EventArgs내가 이벤트 아그의 멤버들 중 누구도 사용할 생각이 없다면 왜 신경쓰겠습니까.
언급URL : https://stackoverflow.com/questions/1046016/event-signature-in-net-using-a-strong-typed-sender
'programing' 카테고리의 다른 글
| C#에서 기본 생성자 호출 (0) | 2023.05.23 |
|---|---|
| onclient click이 false일 때 click이 시작되지 않도록 중지하시겠습니까? (0) | 2023.05.23 |
| iOS 13 전체 화면에서 모달 표시 (0) | 2023.05.18 |
| MongoDB에서 속성을 텍스트에서 날짜 유형으로 변환하려면 어떻게 해야 합니까? (0) | 2023.05.18 |
| Node.js/Mongoose에서 "VersionError: 일치하는 문서를 찾을 수 없습니다" 오류가 발생했습니다. (0) | 2023.05.18 |