调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?

Posted

技术标签:

【中文标题】调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?【英文标题】:When calling a method and assigning returned value to an array, why is C# using the array reference when method was called?调用方法并将返回值分配给数组时,为什么C#在调用方法时使用数组引用? 【发布时间】:2018-10-18 15:25:46 【问题描述】:

我有以下 C# 程序:

class Program
 
    static int[] foo = new int[1];

    static void Main()
    
        foo[0] = Get();
        Console.WriteLine(foo[0]); //print "0"
    

    static int Get()
    
        foo = new int[1];
        return 5;
    

Main() 调用Get() 后,我希望foo 数组在第一个位置包含5。但是,它为零。为什么会这样?

这就像 C# 在调用 Get() 方法时捕获对 foo 的引用。 这个旧的数组引用然后用于分配Get() 返回值(而不是在Get() 中实例化的新值)。

【问题讨论】:

您的问题已包含答案。没有问题要回答了。 为什么选择这样的设计?是执行原因吗?它在一个项目中产生了一个错误,我花了很长时间才发现,因为对我来说,这不是预期的行为。 我会质疑修改数组并返回值以便稍后插入到同一个数组变量中的设计选择。 如果你想知道微软为什么按照他们的方式设计语言,那就问他们,而不是问 SO。老实说,我会说在同一个语句的多个点读取和改变同一个变量是不好的做法,正是因为它会导致这样的错误。您编写的代码仅在以非常特定的顺序计算表达式的顺序时才有效,并且您编写它时假设顺序与实际顺序不同。您编写的代码依赖于在同一语句中观察到的副作用。不要那样做。 【参考方案1】:

语句中的 操作数 总是从左到右进行计算,即使 操作符 的优先规则决定了如何处理复杂的逻辑。这意味着foo[0] 目标实际上是在调用Get 之前执行的。你可以在 IL 中看到这一点:

.method private hidebysig static 
    void Main () cil managed 

    // Method begins at RVA 0x2050
    // Code size 25 (0x19)
    .maxstack 8

    IL_0000: ldsfld int32[] Program::foo
    IL_0005: ldc.i4.0
    IL_0006: call int32 Program::Get()
    IL_000b: stelem.i4
    IL_000c: ldsfld int32[] Program::foo
    IL_0011: ldc.i4.0
    IL_0012: ldelem.i4
    IL_0013: call void [mscorlib]System.Console::WriteLine(int32)
    IL_0018: ret
 // end of method Program::Main

注意ldsfld ... fooldc.i4.0(常数零)在call ... Get() 之前执行,这些值留在堆栈中以供稍后的ldsfld ... foo 使用。在更复杂的情况下(涉及括号等),看看编译器用来保留从左到右求值的技巧可能会非常有趣。像Sharplab这样的工具可以帮助解决这个问题; here's your example in sharplab.

这是由于 ECMA 334 规范中的 12.4 运算符:

12.4.1 概述

...

表达式中运算符的求值顺序由运算符的优先级和关联性决定(第 12.4.2 节)。

表达式中的操作数从左到右计算。 [例子: 在 F(i) + G(i++) * H(i) 中,使用 i 的旧值调用方法 F,然后使用 i 的旧值调用方法 G,并且,最后,使用 i 的新值调用方法 H。这与运算符优先级分开且无关。 结束示例]

【讨论】:

但是 MSDN 说分配是从右到左评估的。 msdn.microsoft.com/en-us/library/2bxt6kc4.aspx 那么这是否违反规范? @Adrian 我 认为 那是在讨论 operator 的评估顺序...而不是 operands . operator 只谈赋值时really 在有多个赋值时才有意义,否则就是final 运算符;但想象一下:bar.x = foo.y = z = 3;;我希望这将按以下顺序进行评估:bar, foo, 3, z=, .y=, .x= @Adrian Precedence, associativity , and the order of evaluation are all different things. 在您链接的页面中,非常明确的是,所有内容的评估顺序始终是从左到右。任何不是从左到右的都是指优先级或关联性。 @Adrian 添加语言规范参考;找到了! @Servy 即使在 C# 中,赋值也是右关联的;这是 ECMA 334 中的 12.4.2(就在它变成 12.4.3 之前);关键是 operator 是右关联的,但是 C# 中的 operands 总是从左到右

以上是关于调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?的主要内容,如果未能解决你的问题,请参考以下文章

从我的 c++ 应用程序调用 c# dll(解析 XML 文件)以将数组/列表返回给 c++

C语言中,数组名作为函数参数,属于啥传递,为啥?

为啥 .NET 的 RandomNumberGenerator.GetBytes 方法将其结果分配给一个字节数组参数,而不是返回一个新的字节数组?

为啥我不能将具有用户定义参数的方法分配给 c# 中的按钮?

为啥 C# 编译器会在最后一个方法调用是条件调用时删除一串方法调用?

java基础3-重载+命令行传参+递归+数组+排序