This is the original source-code written in C#
public delegate Unit UnitResolveEventHandler(object sender, ResolveEventArgs args);
public event UnitResolveEventHandler UnitResolve;
public static Unit GetUnitByName(string name) {
Instance.unitsByName.TryGetValue(name, out result);
if (Instance.UnitResolve != null) {
foreach (UnitResolveEventHandler handler in Instance.UnitResolve.GetInvocationList()) {
result = handler(Instance, new ResolveEventArgs(name));
}
}
}
Using an online translator, I get this VB.NET code:
Public Delegate Function UnitResolveEventHandler(sender As Object, args As ResolveEventArgs) As Unit
Public Event UnitResolve As UnitResolveEventHandler
Public Shared Function GetUnitByName(name As String) As Unit
Instance.unitsByName.TryGetValue(name, result)
If Instance.UnitResolve IsNot Nothing Then
For Each handler As UnitResolveEventHandler In Instance.UnitResolve.GetInvocationList()
result = handler(Instance, New ResolveEventArgs(name))
Next
End If
End Function
The compiler marks the event declaration with this error message:
Events cannot be declared with a delegate type that has a return type.
And the Instance.UnitResolve calls inside the GetUnitByName() method with this error message:
Public Event UnitResolve As UnitResolveEventHandler' is an event, and
cannot be called directly.
How can I properly translate the code from C# to VB.NET without losing functionality?
The customary way of returning a value from an event handler to the invocation of the event is through an argument---either a member of the event arguments class, or through a ByRef parameter on the delegate.
If you have control over ResolveEventArgs and the event handler routines, you could do something like this:
Public Class ResolveEventArgs
'...
Public ReturnValue As Unit
'...
End Class
In the body of your handler (assuming the typical declaration of the event arguments as e), instead of Return (return value):
e.ReturnValue = (return value) 'substitute for (return value) as appropriate
Then, the body of your For Each loop would look like this:
Dim args As New ResolveEventArgs(name)
handler(Instance, args)
result = args.ReturnValue
As an aside, the original C# code has a thread-safety issue. It could throw a NullReferenceException in the event that the last subscribed handler is removed between the null check and reading the invocation list. Whether this is serious (or a concern at all) depends on where and how it's being used. The usual way of addressing this is to store to a temporary, then do the null check and invocation list on the temporary. If you're using a recent version of the .NET languages, then you can skip the null check and use the ?. operator, which should also be safe against the particular thread-safety issue.
The original C# source code is bad; event handlers shouldn’t return values. You’ll have to make it not-an-event:
Public UnitResolve As UnitResolveEventHandler
and use Delegate.Combine manually to add event handler:
Instance.UnitResolve = Delegate.Combine(Instance.UnitResolve, newHandler)
Related
I'm using the AddEventHandler method from that answer, but when doing it on an EventHandler with a value type argument that happens:
using System.Reflection;
public class Program
{
public static event EventHandler<bool> MyEvent;
public static void Main()
{
EventInfo eventInfo = typeof(Program).GetEvent(nameof(MyEvent));
AddEventHandler(eventInfo, null, (s, e) => {
if (e == null) return; // either if condition or null conditional operator
Console.WriteLine(e?.ToString());
});
MyEvent(null, true);
}
public static void AddEventHandler(EventInfo eventInfo, object client, EventHandler handler)
{
object eventInfoHandler = eventInfo.EventHandlerType
.GetConstructor(new[] { typeof(object), typeof(IntPtr) })
.Invoke(new[] { handler.Target, handler.Method.MethodHandle.GetFunctionPointer() });
eventInfo.AddEventHandler(client, (Delegate)eventInfoHandler);
}
}
Any explanation?
You are using undocumented, internal api, and what's even worse is that this api accepts raw pointer. So it's not surprising if things go (horribly) wrong if you misuse such api (and you cannot ever be sure you are using it correctly because it's not documented).
Note that AddEventHandler third parameter is EventHandler, which is delegate of this type:
delegate void EventHandler(object sender, EventArgs e);
And your MyEvent delegate type is:
delegate void EventHandler(object sender, int e);
AddEventHandler uses internal undocumented compiler-generated constructor of delegate which accepts two parameters: delegate target and raw pointer to method. It then just passes raw pointer to the method of handler delegate to that constructor without doing any checks. Delegate you pass and delegate being created can be completely incompatible, but you won't notice that until runtime will try to invoke it.
In this case, runtime thinks it has delegate pointing to method void (object, bool), but actually it points to method void (object, EventArgs). You call it via MyEvent(null, true) and runtime passes true boolean value as second argument (it's value type so value is passed directly), but your delegate actually points to method which expects EventArgs, which is a reference type. So it expects an address of some object of type EventArgs. It gets boolean value as if it was pointer to EventArgs.
Now, == null in general case basically just checks if the reference is zero (all bytes are 0). True boolean value is not represented by 0, so null check passes.
Then, it tries to access object located at this "address". It cannot work, you access protected memory and get access violation error. However, as explained in this answer:
but if (A) the access violation happened at an address lower than
0x00010000 and (B) such a violation is found to have happened by code
that was jitted, then it is turned into a NullReferenceException,
otherwise it gets turned into an AccessViolationException
So it is turned into NullReferenceException you observe.
Interesting that if you change your code like this:
MyEvent(null, false);
Then it will run without errors, because false is represented by zero byte, and so e == null check will return true.
You can play with this code a bit more, for example change event type to int:
public static event EventHandler<int> MyEvent;
And then do:
MyEvent(null, 0x00010001);
Now it will throw AccessViolationException instead of NullReferenceException as the linked answer claims (now we are trying to access memory at location higher than 0x00010000 so runtime does not convert this access violation into null reference exception).
Here is another fun thing, we are using code from this answer to obtain memory address of .NET object at runtime, then pass that address into handler:
public static event EventHandler<IntPtr> MyEvent;
public static unsafe void Main() {
// it's not even EventArgs, it's string
var fakeArgument = "Hello world!";
// some black magic to get address
var typedRef = __makeref(fakeArgument);
IntPtr ptr = **(IntPtr**)(&typedRef);
EventInfo eventInfo = typeof(Program).GetEvent(nameof(MyEvent));
AddEventHandler(eventInfo, null, (object s, EventArgs e) => {
if (e == null) return;
// e is actually a string here, not EventArgs...
Console.WriteLine(e?.ToString());
});
MyEvent(null, ptr);
}
This code outputs "Hello world!", for the reasons explained above.
So long story short - don't use such dangerous undocumented internal apis in real code.
While looking at an UWP example app, I came across this code. What I don't understand and can't seem to find in google is line 40:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
Says in the comments that it is a multicast event. What does assigning delegate { } do? Does it have anything to do with the comment saying the event is multicast?
What does assigning delegate { } do?
That is simply adding an anonymous function to the event, with an empty body. Since C#3, one would generally use a lambda expression instead:
public event PropertyChangedEventHandler PropertyChanged = (s, e) => { };
However, lambda expressions require each parameter to be elicited, whereas using delegate does not. So if you don't need to use the arguments, then delegate may be more concise.
Microsoft highlight this as the single remaining use case of the delegate syntax for defining an anonymous function:
When you use the delegate operator, you might omit the parameter list. If you do that, the created anonymous method can be converted to a delegate type with any list of parameters. That's the only functionality of anonymous methods that is not supported by lambda expressions. In all other cases, a lambda expression is a preferred way to write inline code.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/delegate-operator
Does it have anything to do with the comment saying the event is multicast?
No; multicast means that you can add more than one handling function to the event, and when the event is raised, each one is invoked.
For example:
class Example
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseEvent()
{
PropertyChanged?.Invoke(default, default);
}
}
var example = new Example();
// Use the += operator to add a new handler (rather than = which overwrites)
example += delegate { Console.Writeline("Handler 1"); };
example += delegate { Console.Writeline("Handler 2"); };
example.RaiseEvent();
// Output:
// Handler 1
// Handler 2
Follow up question: What is the purpose of assigning an anonymous function to the event?
If no handler has been added, invoking the event can cause a NullReferenceException, so assigning an empty handler could remove the necessity to handle null.
So the event can be raised like this:
PropertyChanged.Invoke(default, default);
Or this:
PropertyChanged(default, default);
Rather than this:
PropertyChanged?.Invoke(default, default);
It's just an empty delegate, but the compiler will derive delegates from MulticastDelegate. So all delegates in C# are multicast.
Action d = delegate { };
// True
Console.WriteLine(d.GetType().BaseType == typeof(MulticastDelegate));
Using this article, I've set up this COM-visible interface to define my events:
[ComVisible(true)]
[Guid("3D8EAA28-8983-44D5-83AF-2EEC4C363079")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IParserStateEvents
{
void OnParsed();
void OnReady();
void OnError();
}
The events are meant to be fired by a class that implements this interface:
[ComVisible(true)]
public interface IParserState
{
void Initialize(VBE vbe);
void Parse();
void BeginParse();
Declaration[] AllDeclarations { get; }
Declaration[] UserDeclarations { get; }
}
Here's the implementation:
[ComVisible(true)]
[Guid(ClassId)]
[ProgId(ProgId)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComDefaultInterface(typeof(IParserState))]
[ComSourceInterfaces(typeof(IParserStateEvents))]
[EditorBrowsable(EditorBrowsableState.Always)]
public class ParserState : IParserState
{
//...
public event Action OnParsed;
public event Action OnReady;
public event Action OnError;
private void _state_StateChanged(object sender, System.EventArgs e)
{
var errorHandler = OnError; // always null
if (_state.Status == Parsing.VBA.ParserState.Error && errorHandler != null)
{
errorHandler.Invoke();
}
var parsedHandler = OnParsed; // always null
if (_state.Status == Parsing.VBA.ParserState.Parsed && parsedHandler != null)
{
parsedHandler.Invoke();
}
var readyHandler = OnReady; // always null
if (_state.Status == Parsing.VBA.ParserState.Ready && readyHandler != null)
{
readyHandler.Invoke();
}
}
//...
The _state_StateChanged handler is responding to events raised from a background worker thread.
The COM client code is a VBA class looking like this:
Private WithEvents state As Rubberduck.ParserState
Public Sub Initialize()
Set state = New Rubberduck.ParserState
state.Initialize Application.vbe
state.BeginParse
End Sub
Private Sub state_OnError()
Debug.Print "error"
End Sub
Private Sub state_OnParsed()
Debug.Print "parsed"
End Sub
Private Sub state_OnReady()
Debug.Print "ready"
End Sub
While everything looks right from the Object Browser:
...when the VBA code calls BeginParse, breakpoints get hit in the C# code, but all handlers are null, and so the VBA handlers don't run:
What am I doing wrong?
Your COM/VBA integration is about right, however you need to keep in mind COM threading model and rules of using your COM class in single threaded apartment.
You have your instance of Rubberduck.ParserState created on STA thread. VBA immediately sees WithEvents specifier and does its best connecting event handlers to the connection point implemented by COM class. Specifically, COM class receives COM interface pointers to accept event calls on the same thread and stores the pointer to use it later at event invocation time.
When you raise the event, both server (C#) and client (VBA) might or might not check if execution takes place on proper thread (rather, proper apartment). With C++ development you might have a chance to ignore threading mismatch (it is not a good thing but let's assume you know what you're doing), and environments like VBA and .NET COM interop are stricter trying to take care of integrity of environment overall and they are likely to fail if threading is wrong. That is, you have to raise your event on the right thread! If you have a background worker thread, you cannot raise event from it directly and you need to pass it first to the apartment thread where the call is actually expected.
If your threading issue were limited to invocation from worker thread, the problem would rather be non-null event sinks calling which you get an exception or otheriwse the call not reaching your VBA. You have then null however, so it is likely that threading affects in another way (instantiation from certain callback on a worker thread etc.) Either way, once you violate COM rule of not passing interface pointer between apartments, the pointers become unusable, causing failures on calls or being unable to provide expected cast and so on). Having it fixed you will have the events working.
Bonus code: minimal C# project and XLS file proving the events work fine in simplest form (Subversion/Trac).
Event is raised right from Initialize call:
public void Initialize()
{
if (OnReady != null)
OnReady();
}
Private Sub Worksheet_Activate()
If state Is Nothing Then Set state = New ComEvents01.ParserState
' Initialize below will have C# raise an event we'd receive state_OnReady
state.Initialize
End Sub
Private Sub state_OnReady()
' We do reach here from Initialize and Worksheet_Activate
End Sub
I am using delegate to use invoke method with 2 parameters. but it is giving null reference exception error:
Object reference not set to an instance of an object.
Unable to shift control to invoke method in main page. Can anyone tell me why its so..? thanks ..
public override event ResponseRecievedDelegate ResponseRecieved;
if (reqName == REQUEST_NAME.abc)
{
IJsonParser parser = new JsonParser();
Object resp = parser.GetData(responseString );
ResponseRecieved.Invoke(reqName, resp); // unable to invoke this method giving null exception ..
}
invoke method implementation is like:
private void OnResponseReceived(REQUEST_NAME requestName, Object response)
{
if (requestName == REQUEST_NAME.abc)
{
//------------
}
else if (requestName == REQUEST_NAME.def)
{
//------------
}
}
you need to check
if(ResponseRecieved != null)
before calling the event
in general, when using events, before using them we need to check for null. you can read here on the subject
Invoking an event - Once a class has declared an event, it can treat
that event just like a field of the indicated delegate type. The field
will either be null, if no client has hooked up a delegate to the
event, or else it refers to a delegate that should be called when the
event is invoked. Thus, invoking an event is generally done by first
checking for null and then calling the event.
if you want the OnResponseReceived method to be called you need to register it to the event. you can do it like this:
ResponseRecieved += OnResponseReceived;
make sure OnResponseReceived is in the correct format and you do it before you call the event.
another way will be just call the method...
I have this simple event :
public class ClassA
{
public event Func<string, int> Ev;
public int Do(string l)
{
return Ev(l);
}
}
And 2 Methods :
static int Display(string k)
{
return k.Length;
}
static int Display_2(string k)
{
return k.Length*10;
}
Im registering this event :
ClassA a = new ClassA();
a.Ev += Display;
a.Ev += Display_2;
Now , I'm executing :
Console.WriteLine(a.Do("aaa"));
the output :
What ???
he has in invocation list 2 methods ! it did run them , but why does it shows only the result from the last registration ?
Where does the result of "3" has gone ? ( the first invocation ) ? ( although both display+display_2 was executed... I didn't expect console.write to iterate through results . but also didn't expect him to decide which to show.)
edit :
There are three aspects at play here:
The implementation of the event
The behaviour of delegate combination
The behaviour of invoking a delegate whose invocation list has multiple entries
For point 1, you have a field-like event. Section 10.8.1 of the C# 4 spec gives an example, and states that:
Outside the declaration of the Button class, the Click member can be used only on the left-hand saide of the += and -= operators, as in
b.Click += new EventHandler(...);
which appends a delegate to the invocation list of the Click event
(emphasis mine). The spec also makes it clear that a field-like event creates a delegate field, which is used from within the class for invocation.
More generally (point 2), section 7.8.4 of the C# 4 spec talks about delegate combination via + and +=:
Delegate combination. Every delegate type implicitly provides the following predefined operator, where D is the delegate type:
D operator +(D x, D y)
The binary + operato performs delegate combination when both operands are of some delegate type D. [... skip bits where x or y are null ...] Otherwise, the result of the operation is a new delegate that, when invoked, invokes the first operand and then invokes the second operand.
(Again, emphasis mine.)
Finally, point 3 - event invocation and return values. Section 15.4 of the C# spec states:
If the delegate invocation includes output parameters or a return value, their final value will come from the invocation of the last delegate in the list.
More generally, it depends on the event implementation. If you use an event implementation which uses the "normal" delegate combination/removal steps, everything is guaranteed. If you start writing a custom implementation which does crazy things, that's a different matter.
As a general rule, it doesn't make sense for events to return a value.
If you want to get information from an event handler it makes more sense for the event handlers to mutate an input parameter, or just call another method of whatever object fired the event to communicate something else.
Normally this doesn't even come up because events logically are passing information to the event handlers, and don't have any need to get information from the event handlers. It's honestly a sign of code smell. An event shouldn't care if anyone has subscribed to it, who they might be, what they might be doing, or even if there are any subscribers. Relying on a return value from them then just creates overly tight coupling.
Invoking a multicast non-void delegate returns the value of the last handler that was executed.
You have very little control over who is first or last. It is a lousy system to use.
That is why most events and delegates return void.
The events are just iterated in the order they were attached. Because you are using a return value, the value you get is the last invoked.
This is not really a normal pattern for events though. You probably want something a bit more like:
public class MyEventArgs : EventArgs
{
public MyEventArgs()
{
Results = new List<int>();
}
public string InputString{get;set;}
public List<int> Results{get;set;}
}
public event EventHandler<MyEventArgs> Ev
public int Do(string l)
{
MyEventArgs e = new MyEventArgs();
e.InputString = l;
if(Ev != null) Ev(this, e);
return e.Results.Sum();
}
and then
static int Display(object sender, MyEventArgs e)
{
return e.Results.Add(k.Length);
}
static int Display_2(object sender, MyEventArgs e)
{
return e.Results.Add(k.Length*10);
}