I am trying to wrap my head around this behavior that I am seeing. I know that the structs get copied when they are moved around as they are ValueType. But I cannot understand why this sample code fails. Here is just a simple C# code to demonstrate the issue:
public class MyClass
{
private MyStruct ms;
public MyClass()
{
ms = new MyStruct("");
}
public void Add(dynamic d)
{
ms.Add(d);
}
public void Print()
{
Console.WriteLine("L: " ms.GetLength());
}
}
public struct MyStruct
{
private dynamic[] items;
public MyStruct(string junk)
{
items = new dynamic[0];
}
public int GetLength()
{
return items.Length;
}
public void Add(dynamic d)
{
Array.Resize<dynamic>(ref items, items.Length 1);
items[items.Length - 1] = d;
}
}
By running the code below, I get wrong result. In debugger I can see that the item is added but it does not persist, which means I see "L: 0" twice (instead of "L: 0" and "L: 1"):
MyClass mc = new MyClass();
mc.Print();
mc.Add("Test");
mc.Print();
I noticed that if in MyClass.Add, the ms.Add is called with a type other than "dynamic" ("string", for instance) it works fine. Something like this:
public void Add(string d)
{
ms.Add(d);
}
or even this:
public void Add(dynamic d)
{
string s = d.ToString();
ms.Add(s);
}
It makes me think that I am probably working on a copy, but if that is the case, I do not understand why. I am accessing the field directly so I do not expect this to happen.
Thank you very much in advance.
CodePudding user response:
Any operation with a dynamic
type (including call with a dynamic
parameter) uses runtime-binding and thus ultimately reflection - and as @AlexeiLevenkov noted in his comment this involves boxing.
You can prove this by adding an overload
public void Add(string d)
{
Console.WriteLine("Add(String)");
Array.Resize<dynamic>(ref items, items.Length 1);
items[items.Length - 1] = d;
}
to MyStruct
. This produces the output
L: 0
AddString
L: 0
If you have a look at the generated IL, this includes:
ldfld MyClass.ms
ldarg.1
callvirt Action <CallSite, MyStruct, Object>.Invoke (CallSite, MyStruct, Object)
So the struct will be passed as parameter to an Action ultimatedly performing the call to the dynamically determined overload of ms.Add
. This parameter passing is where a copy is created.
CodePudding user response:
There are several issues with your structures. But that's OK, everyone had to go through this process.
- Why are you using
MyClass
as class, butMyStruct
as struct? Fundamentally, your structure has no difference than a class, so its better to define it as its own class. - Is there a particular reason why you are using
Array.Resize<dynamic>(ref items, items.Length 1);
insideMyStruct
? Why don't you use a simpleList
? If you use an array, you will have a hard time to re-define its size. Use List, and then just use add. - There is a keyword: const that you can use to make your parameters constant, and not get copied when called as a parameter of a function. It might help you in the future.
- In your class
Myclass
, you should use the reserved keyword this to clarify that you are referring to the inner object of the class
public void Add(string d)
{
this.ms.Add(d);
}
- If you are new to C# or coming from another background (C or C or Java), its better to not use dynamic that loosely. You might get some serious memory allocation problems if you don't have experience with it. It's better if you decide/know the type before hand. If you don't but you know what could be the types, you can use an if with IsInstanceOfType.