首先,在 C# 中,默认情况下所有参数都按值传递。这与类型是引用类型还是值类型无关,两者的行为方式完全相同。
现在,问题是,什么是变量? 变量是值的占位符,仅此而已。当通过副本传递变量时,值的副本被制作。
存储在变量中的值是什么?好吧,如果变量的类型是引用类型,那么值基本上就是它所引用的对象的内存地址。如果是值类型,那么值就是对象本身。
所以当你说:
似乎在任何一种情况下,如果变量在函数中被更改*,它也会在外部范围内被更改。
这是非常错误的,因为在我看来,您似乎将参数的类型与其传递方式混为一谈:
第一个例子:
var a = new object();
Foo(a);
var isNull = ReferenceEquals(a, null); //false!
void Foo(object o) { o = null; }
这里,一个引用类型变量a通过值传递,复制一个然后在Foo内部重新分配给null。 a 不在乎 copy 在 Foo 内被重新签名,它仍然会指向同一个对象。
如果你通过引用传递参数,事情当然会改变:
var a = new object();
Foo(ref a);
var isNull = ReferenceEquals(a, null); //true!
void Foo(ref object o) { o = null; }
现在您不是在复制名为 o 的 a,而是通过名为 o 的别名传递 a 本身。
值类型的行为完全相同:
var a = 1;
Foo(a);
var isNull = 1 == 0; //false!
void Foo(int i) { i = 0; }
和
var a = 1;
Foo(ref a);
var isNull = 1 == 0; //true!
void Foo(ref int i) { i = 0; }
值类型和引用类型之间的差异,当你通过值传递它时,是由于变量的值是什么。正如我们之前所说,引用类型变量存储地址,因此即使您传递一个副本,该副本也指向同一个对象,因此对象的任何更改都可以从两个变量中看到:
var ii = new List<int>();
Foo(ii);
var b = ii.Count == 1; //true!
void Foo(List<int> list) { list.Add(1); }
但是对于值类型,值就是对象本身,因此您传递对象的副本,因此您正在修改副本:
struct MutableStruct
{
public int I { get; set; }
}
var m = new mutableStruct();
Foo(m);
var b = m.I == 1; //false!
void Foo(MutableStruct mutableStruct) { mutableStruct.I = 1; }
这会让事情更清楚吗?