在 C# 中,当变量通过函数/方法时,原始变量会更改吗?

In C#, when a variable passes through a function/method, will the original variable change?

提问人:Joey 提问时间:4/17/2022 最后编辑:D-ShihJoey 更新时间:5/1/2022 访问量:924

问:

我对函数如何更改通过它的变量感到困惑。例如,如果我创建了一个变量 t = 1,并通过向其添加 2 来传递一个函数,则函数 t 内部为 3,但在 Main 函数中 t 仍然是 1:

static void Main(string[] args)
{
    int t = 1;
    addTwo(t);
    Console.WriteLine(t); 
    ## t=1
    Console.ReadLine();
}
static void addTwo(int t)
{
    t+=2;
    Console.WriteLine("inside function {0}",t); 
    ## t=3

函数中 t 的值为 3,但在 Main 中,该值保持为 1。

但是,如果我创建了一个值为 {2,3,4,5,6} 的数组“happiness”,并传递了一个函数“SunIsShining”,该函数将每个值增加 2。之后,我认为数组幸福应该仍然是 {2,3,4,5,6}。然而,它变成了{4,5,6,7,8}。

static void Main(string[] args)
{
    int[] happiness = { 2, 3, 4, 5, 6 };
    SunIsShining(happiness);
    ## happiness = { 4, 5, 6, 7, 8 }

    foreach (int y in happiness)
    {
        Console.WriteLine(y);
    }
    Console.ReadLine();
}
static void SunIsShining(int[] x)
{
    for (int i = 0; i < x.Length; i++)
        x[i] += 2;
}

谁能帮我理解原因?谢谢!

C# OOP 按引用传递

评论

1赞 gcores 4/17/2022
learn.microsoft.com/en-us/dotnet/csharp/programming-guide/......
0赞 juharr 4/17/2022
请注意,虽然如果在方法中更改了数组中的值,则不会更改为空数组。x = new int[0];happiness

答:

4赞 D-Shih 4/17/2022 #1

因为

  • int[]是可能将对象引用传递给函数的引用类型,因此您可以修改同一数组中的值。

  • int是一个值类型,它将在传递给函数之前克隆值,因此您可以修改值不是来自函数。tMain

我们可以通过这个示例代码通过比较对象引用的方法证明是否与下面相同,假设我们可以看到 是 return ,但返回 true。ReferenceEqualsaddTwofalseSunIsShining

static int t1;
static int[] happiness;
static void Main(string[] args)
{
    t1 = 1;
    happiness = new int[]{ 2, 3, 4, 5, 6 };
    
    addTwo(t1);
    SunIsShining(happiness);
    
    Console.ReadLine();
}
static void addTwo(int t)
{   
    t+=2;
    Console.WriteLine("Is same object by value?" + object.ReferenceEquals(t1,t)); 
}

static void SunIsShining(int[] x)
{
    Console.WriteLine("Is same object by refer?" + object.ReferenceEquals(happiness,x)); 
}

C# 联机

我们可以看到的更多信息

值类型

引用类型

传递参数

评论

0赞 Joey 4/17/2022
谢谢!您知道在哪里可以找到“引用类型”和“值类型”的列表,或者任何介绍这两者之间差异的 Microsoft 文档吗?
1赞 D-Shih 4/17/2022
@Joey我在回答中添加了更多详细信息并参考文档,希望对您有所帮助
1赞 Mr.D 4/17/2022 #2

变量传递分为值传递和地址传递。
值传递不能改变上一个值,地址传递就是传递地址,所以可以改变上一个值。
例如,第一个由变量的名称传递。T 存储一个 int,因此它传递一个数据,因此它在函数中传递一个 1,与 Main 中的 t 无关。Pass 是 Behind 的值
;我们传递数组的名称,数组的名称是数组中第一个元素的地址,因此我们传递地址,因此我们通过地址访问它,更改原始值

评论

0赞 Enigmativity 4/17/2022
“传递数组的名称”不正确。这根本不是发生的事情。
1赞 newacct 5/1/2022 #3

两个示例之间的语义差异不是由不同的类型来解释的,而是由您在两个示例中对参数执行不同操作来解释的。

在第一个示例中,您将分配给参数 ( 等效于 。 是分配给 。在第二个示例中,您不将 ;相反,您正在使用 array 元素语法来修改 指向的数组的内部内容。如果您在第二个示例中执行了与第一个示例相同的操作,即直接赋值给参数,您会看到被调用的范围同样不受赋值的影响:t += 2t = t + 2t = somethingtxxx

static void Main(string[] args) {
    int[] happiness = { 2, 3, 4, 5, 6 };
    SunIsShining(happiness);

    foreach (int y in happiness) {
        Console.WriteLine(y); // prints 2, 3, 4, 5, 6
    }
}
static void SunIsShining(int[] x) {
    x = new int[] {7, 8, 9};
}

因此,尽管您的第一个示例传递了 值类型,而此示例传递了 引用类型,但您可以看到,在这两种情况下,分配给被调用函数中的参数的结果都是相同的。事实上,对于所有其他类型也是如此 -- 如果方法参数没有标记 或 ,那么无论参数是什么类型,在被调用函数中直接赋值给该参数变量永远不会对调用函数中传递的变量产生任何影响。intint[]refout

将变量传递给方法参数,该参数与赋值给新变量不同或完全相同。因此,与其考虑传递给方法,不如考虑赋值给新变量时会发生什么。当您为第一个示例执行此操作时,您将一个分配给另一个示例。由于是值类型,因此每个 int 变量都保留自己的整数,赋值会复制整数,因此两个变量的整数具有相同的值。然后,当您分配给第二个变量时,您可以更改该整数,但它不会影响第一个变量的整数。这很容易理解。refoutintint

int tInMain = 1;

int tInAddTwo = tInMain;
tInAddTwo += 2;
Console.WriteLine("inside function {0}", tInAddTwo); // prints 3

Console.WriteLine(tInMain); // prints 1

如果你用我上面的例子来做这件事,你会看到我们将一个变量分配给另一个变量。 是一个引用类型,所以这种变量的值是一个“引用”(基本上,用C++的话来说,是一个指向对象的指针)。当您将一个引用复制到另一个引用时,您会得到两个具有相同值的引用,即它们都指向同一个对象。然后,当您将对另一个对象的引用分配给其中一个变量时,只需使该变量指向该另一个对象即可。它不会对它曾经指向的对象(第一个变量仍然指向该对象)执行任何操作。int[]int[]

int[] happiness = { 2, 3, 4, 5, 6 };

int[] x = happiness;
x = new int[] {7, 8, 9};

foreach (int y in happiness) {
    Console.WriteLine(y); // prints 2, 3, 4, 5, 6
}

如果你在第二个示例中这样做,你会看到你再次将一个引用分配给另一个引用,所以你有两个引用变量指向同一个对象。但是,然后使用其中一个引用对所指向的对象的内容进行更改。然后,当您查看其他引用所指向的对象时,这种更改显然是可见的,因为它与所指向的对象相同。

int[] happiness = { 2, 3, 4, 5, 6 };

int[] x = happiness;
for (int i = 0; i < x.Length; i++)
    x[i] += 2;

foreach (int y in happiness)
{
    Console.WriteLine(y); // prints 4, 5, 6, 7, 8
}

现在你可能会问为什么你不能用你的第一个例子做这样的事情。原因是您正在执行的操作,即“修改引用指向的对象”,对于类型根本不存在。最明显的是,这是因为是一种值类型。但是,即使对于引用类型,也可能无法执行此操作,因为所指向的对象可能无法提供修改其内容的方法。例如,考虑以下示例,我们将 装箱 into 一个类型为 的对象变量:intintintobject

static void Main(string[] args) {
    object t = 1;
    addTwo(t);
    Console.WriteLine(t); // prints 1
}
static void addTwo(object t) { // <-- object is a reference type
    // there is nothing you can do to t here
    // that will change what is printed in Main

    // as described above, assignment to t
    // will never affect the calling scope:
    t = (int)t + 2;
}

这与第一个示例类似,不同之处在于参数类型是 ,一个引用类型(对对象的引用类型)。但是这个被装箱的对象根本没有提供修改其内容的方法。因此,即使在本例中,您传递了一个引用,并且 in 和 in 指向单个对象,这并不重要,因为您无法对可以通过另一个引用看到的该对象进行更改。objectinttaddTwotMain