调用带有动态参数的结构体方法会导致其被复制吗?

3

我试图理解这种行为。 我知道结构体是值类型,移动时会被复制。 但是我无法理解为什么此示例代码失败。这是一个简单的C#代码,以演示这个问题:

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;
        }
    }

通过运行以下代码,我得到了错误的结果。在调试器中,我可以看到项目已添加,但它没有持久化,这意味着我会看到两次“L: 0”(而不是“L: 0”和“L: 1”):
MyClass mc = new MyClass();
mc.Print();
mc.Add("Test");
mc.Print();

我注意到,如果在 MyClass.Add 中调用 ms.Add 时使用的类型不是 "dynamic"(例如 "string"),它可以正常工作。类似于下面这样:

public void Add(string d)
{
   ms.Add(d);
}

或者甚至是这样:

public void Add(dynamic d)
{
   string s = d.ToString();
   ms.Add(s);
}

这让我想到我可能正在处理一份副本,但如果是这种情况,我不明白为什么会这样。我直接访问该字段,因此不希望出现这种情况。

非常感谢您的提前帮助。


反射总是涉及装箱 - 你为什么期望不同呢?(你拥有的结构体相当疯狂...拥有可变值类型和部分引用语义...会让你的生活变得有趣)。 - Alexei Levenkov
在运行时,dynamic == object,只是所有成员访问的运行时绑定。因此,dynamic也意味着您的结构值将被装箱。 - Jeremy Lakeman
请不要使用 string s = d.ToString();。当 ddynamic 类型时,这样做是不好的。 - Enigmativity
2个回答

1
任何使用dynamic类型的操作(包括使用dynamic参数进行调用)都会使用运行时绑定,因此最终涉及到反射 - 正如@AlexeiLevenkov在他的评论中所指出的,这涉及到装箱。
您可以通过添加一个重载方法来证明这一点。
public void Add(string d)
{
    Console.WriteLine("Add(String)");
    Array.Resize<dynamic>(ref items, items.Length + 1);
    items[items.Length - 1] = d;
}

转换为MyStruct。 这将产生以下输出

L: 0 
AddString 
L: 0

如果您查看生成的IL代码,其中包括:

如果您查看生成的IL代码,其中包括:

ldfld   MyClass.ms
ldarg.1 
callvirt    Action <CallSite, MyStruct, Object>.Invoke (CallSite, MyStruct, Object)

因此,结构体将作为参数传递给一个Action,最终执行对动态确定的ms.Add重载的调用。这个参数传递是创建副本的地方。


非常感谢大家的解释。 - S. J.

0

你的结构存在几个问题。但没关系,每个人都必须经历这个过程。

  1. 为什么你使用 MyClass 作为类名,而使用 MyStruct 作为结构体名?从根本上讲,你的结构与类没有区别,所以最好将其定义为自己的类。
  2. 你在 MyStruct 中为什么要使用 Array.Resize<dynamic>(ref items, items.Length + 1);?为什么不使用简单的 List?如果你使用数组,重新定义其大小会很困难。使用 List,然后只需使用 add。
  3. 有一个关键字:const,你可以使用它来使你的参数常量,并且在作为函数参数调用时不会被复制。这可能会在未来对你有所帮助。
  4. 在你的类 Myclass 中,你应该使用保留关键字 this 来澄清你正在引用类的内部对象。
public void Add(string d)
{
   this.ms.Add(d);
}
  1. 如果你是C#的新手或者来自其他背景(如C、C++或Java),最好不要过于随意地使用dynamic。如果你没有经验,可能会遇到一些严重的内存分配问题。最好在使用之前决定/知道类型。如果你不知道类型,但知道可能的类型,可以使用带有IsInstanceOfTypeif语句。

我不同意列表中的第一项。类在内存中的存储方式与结构体不同,这可能会导致显著的性能差异,特别是如果对象仅在范围内存在很短的时间。我不会做出一个一般性的声明,即类比结构体更好。 - Sebastian Schumann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接