Home > Software design >  Does calling a struct method with a dynamic parameter cause it to get copied?
Does calling a struct method with a dynamic parameter cause it to get copied?

Time:11-16

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.

  1. Why are you using MyClass as class, but MyStruct as struct? Fundamentally, your structure has no difference than a class, so its better to define it as its own class.
  2. Is there a particular reason why you are using Array.Resize<dynamic>(ref items, items.Length 1); inside MyStruct? Why don't you use a simple List? If you use an array, you will have a hard time to re-define its size. Use List, and then just use add.
  3. 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.
  4. 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);
}
  1. 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.
  • Related