M
Michi Henning
Below is a bit of code that creates a delegate.
The delegate invokes a member function of a struct.
The code compiles, but has surprising behavior:
using System;
namespace ConsoleApplication1
{
public struct SimpleStruct
{
public int i;
public void assignToI(int x)
{
Console.WriteLine("Assigning i: " + x);
i = x;
Console.WriteLine("i = " + x);
}
}
class Class1
{
public delegate void Assigner(int s);
private static void callDelegate(Assigner a, int i)
{
a(i);
}
static void Main(string[] args)
{
SimpleStruct s = new SimpleStruct();
s.assignToI(1); // Works as expected, assigns 1 to s.i
System.Console.WriteLine("s.i after calling assignToI = " + s.i);
// Create a delegate that calls assignToI()
Assigner assign_to_i = new Assigner(s.assignToI);
// Call helper function, passing delegate and new value for s.i
callDelegate(assign_to_i, 99);
System.Console.WriteLine("s.i after calling delegate = " + s.i);
// Assignment is lost, s.i still contains 1!!!
}
}
}
When run, this code prints:
$ ./ConsoleApplication1.exe
Assigning i: 1
i = 1
s.i after calling assignToI = 1
Assigning i: 99
i = 99
s.i after calling delegate = 1
Note that the direct call s.assignToI() works as expected:
after the call, the structure member has new value.
However, the call via the delagate does not work as expected:
the value that is assigned is lost, and the structure member
has the old value.
Now, I realize what is going on here. The delegate constructor
expects an argument of type object. In other words, the expression
new Assigner(s.assignToI)
is internally converted to something like
new Delegate(s, "assignToI")
So, the struct is silently boxed and, when the delegate runs,
what it assigns to is a temporary copy of the struct on the heap,
instead of assigning to the real struct.
Several thoughts here:
1) I find this surprising. At no point in my code have I passed the
structure as a parameter, so I don't expect this behavior. (Yes,
I know that the struct ends up being boxed, but that is not manifest
anywhere in the code.)
2) I believe that the behavior is wrong. (Yes, I understand *why* it
works the way it does, but that doesn't necessarily make the behavior
right.) Delegates are the equivalent of C++ member function pointers.
If I create a delegate and pass a method of a struct, I expect the
delegate to call the method on the struct instance I specified, not
some temporary copy.
3) Even if we accept that the behavior is correct, then why does the compiler
allow me to write this? After all, there is no way that such a delegate would
ever do something useful, so why not at least emit a warning?
4) The silent boxing and unboxing in C# seems to be more of a curse than helpful.
It is too easy to have a value type boxed, only to end up with invocations
made to a boxed copy on the heap.
Finally, I'm looking for suggestions as to how I can achieve what the above
code is trying to do. Basically, what I need to do is assign to a member of
an already instantiated structure, but without knowing the type of the structure.
That is, I want to, at least in spirit, be able to assign to a structure member
via a pointer to the structure.
(This issue arises in the context of unmarshaling data from the wire and, for
various legitimate reasons, I have to assign to a member of an already instatiated
structure, instead of instantiating the structure after I have all its member values.)
I tried using pointers and unsafe code, but that only works for types that are unmanaged.
However, the structure may contain managed members, in which case I can no
longer create a pointer to the struct, so this doesn't work either.
Any other ideas anyone?
Thanks,
Michi.
The delegate invokes a member function of a struct.
The code compiles, but has surprising behavior:
using System;
namespace ConsoleApplication1
{
public struct SimpleStruct
{
public int i;
public void assignToI(int x)
{
Console.WriteLine("Assigning i: " + x);
i = x;
Console.WriteLine("i = " + x);
}
}
class Class1
{
public delegate void Assigner(int s);
private static void callDelegate(Assigner a, int i)
{
a(i);
}
static void Main(string[] args)
{
SimpleStruct s = new SimpleStruct();
s.assignToI(1); // Works as expected, assigns 1 to s.i
System.Console.WriteLine("s.i after calling assignToI = " + s.i);
// Create a delegate that calls assignToI()
Assigner assign_to_i = new Assigner(s.assignToI);
// Call helper function, passing delegate and new value for s.i
callDelegate(assign_to_i, 99);
System.Console.WriteLine("s.i after calling delegate = " + s.i);
// Assignment is lost, s.i still contains 1!!!
}
}
}
When run, this code prints:
$ ./ConsoleApplication1.exe
Assigning i: 1
i = 1
s.i after calling assignToI = 1
Assigning i: 99
i = 99
s.i after calling delegate = 1
Note that the direct call s.assignToI() works as expected:
after the call, the structure member has new value.
However, the call via the delagate does not work as expected:
the value that is assigned is lost, and the structure member
has the old value.
Now, I realize what is going on here. The delegate constructor
expects an argument of type object. In other words, the expression
new Assigner(s.assignToI)
is internally converted to something like
new Delegate(s, "assignToI")
So, the struct is silently boxed and, when the delegate runs,
what it assigns to is a temporary copy of the struct on the heap,
instead of assigning to the real struct.
Several thoughts here:
1) I find this surprising. At no point in my code have I passed the
structure as a parameter, so I don't expect this behavior. (Yes,
I know that the struct ends up being boxed, but that is not manifest
anywhere in the code.)
2) I believe that the behavior is wrong. (Yes, I understand *why* it
works the way it does, but that doesn't necessarily make the behavior
right.) Delegates are the equivalent of C++ member function pointers.
If I create a delegate and pass a method of a struct, I expect the
delegate to call the method on the struct instance I specified, not
some temporary copy.
3) Even if we accept that the behavior is correct, then why does the compiler
allow me to write this? After all, there is no way that such a delegate would
ever do something useful, so why not at least emit a warning?
4) The silent boxing and unboxing in C# seems to be more of a curse than helpful.
It is too easy to have a value type boxed, only to end up with invocations
made to a boxed copy on the heap.
Finally, I'm looking for suggestions as to how I can achieve what the above
code is trying to do. Basically, what I need to do is assign to a member of
an already instantiated structure, but without knowing the type of the structure.
That is, I want to, at least in spirit, be able to assign to a structure member
via a pointer to the structure.
(This issue arises in the context of unmarshaling data from the wire and, for
various legitimate reasons, I have to assign to a member of an already instatiated
structure, instead of instantiating the structure after I have all its member values.)
I tried using pointers and unsafe code, but that only works for types that are unmanaged.
However, the structure may contain managed members, in which case I can no
longer create a pointer to the struct, so this doesn't work either.
Any other ideas anyone?
Thanks,
Michi.