.NET의 이벤트 서명 — 강력한 유형의 '보낸 사람'사용?
나는 내가 제안하는 것이 .NET 지침을 따르지 않는다는 것을 완전히 알고 있으며, 따라서 이러한 이유만으로는 아마도 좋지 않은 생각 일 것입니다. 그러나 두 가지 가능한 관점에서 이것을 고려하고 싶습니다.
(1) 100 % 내부 용으로 개발 작업에 사용하는 것을 고려해야합니다.
(2) 이것은 프레임 워크 설계자가 변경 또는 업데이트를 고려할 수있는 개념입니까?
현재 .NET 디자인 패턴 인 '객체'로 입력하는 대신 강력한 유형의 '보낸 사람'을 활용하는 이벤트 서명을 사용할 생각입니다. 즉, 다음과 같은 표준 이벤트 서명을 사용하는 대신 :
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
다음과 같이 강력한 형식의 '보낸 사람'매개 변수를 사용하는 이벤트 서명을 사용하는 것을 고려하고 있습니다.
먼저 "StrongTypedEventHandler"를 정의합니다.
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
이것은 Action <TSender, TEventArgs>와 크게 다르지 않지만를 사용 StrongTypedEventHandler
하여 TEventArgs가 System.EventArgs
.
다음으로, 예를 들어 다음과 같이 퍼블리싱 클래스에서 StrongTypedEventHandler를 사용할 수 있습니다.
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으로 돌아가는 것은 상상할 수 없습니다.)
다른 사람이 이것에 대한 문제를 생각할 수 있습니까? 아니면 이것은 단순히 관습을 너무 많이 깨뜨려 사람들의 배를 돌리게하는 것일까 요?
내가 찾은 관련 링크는 다음과 같습니다.
(2) C # 단순 이벤트 발생- "보낸 사람"대 사용자 지정 EventArgs 사용 [StackOverflow 2009]
(3) .net의 이벤트 시그니처 패턴 [StackOverflow 2008]
나는 이것에 대한 모든 사람의 의견에 관심이 있습니다 ...
미리 감사드립니다.
마이크
편집 # 1 : 이것은 Tommy Carlier의 게시물 에 대한 응답입니다 .
Here's a full working example that shows that both strong-typed event handlers and the current standard event handlers that use a 'object sender' parameter can co-exist with this approach. You can copy-paste in the code and give it a run:
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에서 테스트를 해봤는데, 확실히하기 위해 다음과 같이 컴파일되지 않습니다.
Method 'Public Sub SomeEventHandler(sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' does not have the same signature as delegate 'Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As System.EventArgs)(sender As Publisher, e As PublisherEventArgs)'
Basically, delegates are invariant in VB.NET versions 2005 and below. I actually thought of this idea a couple of years ago, but VB.NET's inability to deal with this bothered me... But I've now moved solidly to C#, and VB.NET can now handle it, so, well, hence this post.
Edit: Update #3
Ok, I have been using this quite successfully for a while now. It really is a nice system. I decided to name my "StrongTypedEventHandler" as "GenericEventHandler", defined as follows:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Other than this renaming, I implemented it exactly as discussed above.
It does trip over FxCop rule CA1009, which states:
"By convention, .NET events have two parameters that specify the event sender and event data. Event handler signatures should follow this form: void MyEventHandler( object sender, EventArgs e). The 'sender' parameter is always of type System.Object, even if it is possible to employ a more specific type. The 'e' parameter is always of type System.EventArgs. Events that do not provide event data should use the System.EventHandler delegate type. Event handlers return void so that they can send each event to multiple target methods. Any value returned by a target would be lost after the first call."
Of course, we know all this, and are breaking the rules anyway. (All event handlers can use the standard 'object Sender' in their signature if preferred in any case -- this is a non-breaking change.)
So the use of a SuppressMessageAttribute
does the trick:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
I hope that this approach becomes the standard at some point in the future. It really works very nicely.
Thanks for all your opinions guys, I really appreciate it...
Mike
It seems Microsoft has picked up on this as a similar example is now on MSDN:
What you're proposing does make alot of sense actually, and I just wonder if this is one of those things that's simply the way it is because it was originally designed before generics, or if there's a real reason for this.
The Windows Runtime (WinRT) introduces a TypedEventHandler<TSender, TResult>
delegate, which does exactly what your StrongTypedEventHandler<TSender, TResult>
does, but apparently without the constraint on the TResult
type parameter:
public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
TResult args);
The MSDN documentation is here.
I take issue with the following statements:
- I believe that the older versions of Visual Basic .NET through 2005 do not have delegate covariance and contravariance.
- I do fully realize that this verges on blasphemy.
First of all, nothing you have done here has anything to do with covariance or contravariance. (Edit: The previous statement is wrong, for more information please see Covariance and Contravariance in Delegates) This solution will work just fine in all CLR versions 2.0 and up (obviously this will not work in a CLR 1.0 application as it uses generics).
Secondly, I strongly disagree that your idea verges on "blasphemy" as this is a wonderful idea.
I took a peek at how this was handled with the new WinRT and based on other opinions here, and finally settled on doing it like this:
[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
TSender sender,
TEventArgs e
) where TEventArgs : EventArgs;
This seems to be the best way forward considering the use of the name TypedEventHandler in WinRT.
I think it is a great idea and MS might simply not have the time or interest to invest in making this better as for example when they moved from ArrayList to generic based lists.
From what I understand, the "Sender" field is always supposed to refer to the object which holds the event subscription. If I had my druthers, there would also be a field holding information sufficient to unsubscribe an event should it become necessary(*) (consider, for example, a change-logger which subscribes to 'collection-changed' events; it contains two parts, one of which does the actual work and holds the actual data, and the other of which provides a public interface wrapper, the main part could hold a weak reference to the wrapper part. If the wrapper part gets garbage-collected, that would mean there was no longer anybody interested in the data that was being collected, and the change-logger should thus unsubscribe from any event it receives).
Since it's possible that an object may send events on behalf of another object, I can see some potential usefulness for having a "sender" field which is of Object type, and for having the EventArgs-derived field contain a reference to the object which should be acted upon. The usefuless of the "sender" field, however, is probably limited by the fact that there's no clean way for an object to unsubscribe from an unknown sender.
(*) Actually, a cleaner way of handling unsubscriptions would be to have a multicast delegate type for functions which return Boolean; if a function called by such a delegate returns True, the delegate would be patched to remove that object. This would mean that delegates would no longer be truly immutable, but it should be possible to effect such change in thread-safe manner (e.g. by nulling out the object reference and having the multicast delegate code ignore any embedded null object references). Under this scenario, an attempt to publish and event to a disposed object could be handled very cleanly, no matter where the event came from.
Looking back to blasphemy as the only reason for making sender an object type (if to omit problems with contravariance in VB 2005 code, which is a Microsoft's blunder IMHO), can anyone suggest at least theoretical motive for nailing the second argument to EventArgs type. Going even further, is there a good reason to conform with Microsoft's guidelines and conventions in this particular case?
Having need to develop another EventArgs wrapper for another data that we want to pass inside event handler seems odd, why can't straightly pass that data there. Consider the following sections of code
[Example 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;
}
[Example 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;
}
With the current situation (sender is object), you can easily attach a method to multiple events:
button.Click += ClickHandler;
label.Click += ClickHandler;
void ClickHandler(object sender, EventArgs e) { ... }
If sender would be generic, the target of the click-event would not be of type Button or Label, but of type Control (because the event is defined on Control). So some events on the Button-class would have a target of type Control, others would have other target types.
I don't think there's anything wrong with what you want to do. For the most part, I suspect that the object sender
parameter remains in order to continue to support pre 2.0 code.
If you really want to make this change for a public API, you might want to consider creating your own base EvenArgs class. Something like this:
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; } }
}
Then you can declare your events like this
public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;
And methods like this:
private void HandleSomething(object sender, EventArgs e)
will still be able to subscribe.
EDIT
That last line made me think a bit... You should actually be able to implement what you propose without breaking any outside functionality since the runtime has no problem downcasting parameters. I would still lean toward the DataEventArgs
solution (personally). I would do so, however knowing that it is redundant, since the sender is stored in the first parameter and as a property of the event args.
One benefit of sticking with the DataEventArgs
is that you can chain events, changing the sender (to represent the last sender) while the EventArgs retains the original sender.
Go for it. For non component based code, I often simplify Event signatures to be simply
public event Action<MyEventType> EventName
where MyEventType
does not inherit from EventArgs
. Why bother, if I never intend to use any of the members of EventArgs.
참고URL : https://stackoverflow.com/questions/1046016/event-signature-in-net-using-a-strong-typed-sender
'program story' 카테고리의 다른 글
JVM이 JIT 컴파일 된 코드를 캐시하지 않는 이유는 무엇입니까? (0) | 2020.08.10 |
---|---|
bower init를 수행 할 때 "주 파일"속성은 무엇입니까? (0) | 2020.08.10 |
Android Whatsapp / 채팅 예제 (0) | 2020.08.10 |
스레드 컨텍스트 전환 대. (0) | 2020.08.09 |
Android에서 마이너스 마진을 사용하는 것이 나쁜 습관입니까? (0) | 2020.08.09 |