.NET参数传递 – 按值引用v / s
我试图validation我对C#/ .NET / CLR如何处理值类型和引用类型的理解。 我已经阅读了很多相互矛盾的解释
这就是我今天所理解的,如果我的假设是错误的,请纠正我。
诸如int等的值类型存在于堆栈上,引用类型存在于托管堆上, 但是如果引用类型具有例如double类型的实例变量,则它将与其对象一起存在于堆上
第二部分是我最困惑的。
让我们考虑一个名为Person的简单类。
Person有一个名为Name的属性。
假设我在另一个类中创建了Person实例,我们称之为UselessUtilityClass。
请考虑以下代码:
class UselessUtilityClass { void AppendWithUnderScore(Person p) { p.Name = p.Name + "_"; } }
然后我们做的地方:
Person p = new Person(); p.Name = "Priest"; UselessUtilityClass u = new UselessUtilityClass(); u.AppendWithUnderScore(p);
Person是一个引用类型,当传递给UselessUtilityClass时 – 这就是我去的地方 – 坚果……作为Person引用实例的VARIABLE p由VALUE传递,这意味着当我写p.Name时我会看到“牧师_”
如果我写的话
Person p2 = p;
我做到了
p2.Name =“不是牧师”;
写下p的名字就像下面我会得到“不是牧师”
Console.WriteLine(p.Name) // will print "Not a Priest"
这是因为它们是引用类型并指向内存中的相同地址。
我的理解是否正确?
我认为当人们说.NET中的所有对象都通过Reference传递时会出现一些误解,这根据我的想法并不存在。 我错了,这就是为什么我来到堆垛机。
诸如int等的值类型存在于堆栈中。 引用类型存在于托管堆上, 但是如果引用类型具有类型为double的实例变量,则它将与其在堆上的对象一起存在
不,这不对。 一个正确的语句是“在CLI的Microsoft实现中,执行线程的系统堆栈上分配的局部变量和值类型的forms参数既不直接在迭代器块中,也不在λ或匿名方法的封闭外部变量中分配和Microsoft的C#实现。“
不要求任何版本的C#或任何版本的CLI使用系统堆栈进行任何操作。 当然我们这样做是因为它是一个方便的数据结构,用于局部变量和值类型的forms参数,它们不直接在迭代器块或lambda或匿名方法的封闭外部变量中。
请参阅我关于此主题的文章,讨论(1)为什么这是一个实现细节,(2)我们从这个实现选择中获得了什么好处,以及(3)实现这个实现选择的愿望有哪些限制驱动到语言设计。
http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx
Person是一个引用类型,当传递给UselessUtilityClass时 – 这就是我去的地方 – 坚果……
深吸一口气。
变量是存储位置。 每个存储位置都有一个关联的类型。
其关联类型是引用类型的存储位置可以包含对该类型的对象的引用 ,或者可以包含空引用。
关联类型为值类型的存储位置始终包含该类型的对象。
变量的值是存储位置的内容 。
作为Person引用的实例的VARIABLE p由VALUE传递,
变量p是存储位置。 它包含对Person实例的引用。 因此,变量的值是对Person的引用。 该值 – 对实例的引用 – 被传递给被调用者。 现在,您混淆的另一个变量也称为“p”,它包含相同的值 – 该值是对特定对象的引用。
现在,也可以传递对变量的引用,许多人发现这个变量令人困惑。 当你说时,更好的思考方式就是这样
void Foo(ref int x) { x = 10; } ... int p = 3456; Foo(ref p);
这意味着“x是变量p的别名”。 也就是说,x和p是同一变量的 两个名称 。 因此无论p的值是什么,这也是x的值,因为它们是同一存储位置的两个名称。
现在有道理吗?
诸如int等的值类型存在于堆栈上,引用类型存在于托管堆上,但是如果引用类型具有例如double类型的实例变量,则它将与其对象一起存在于堆上
正确。
您还可以将其描述为实例变量,它们是为堆上的实例分配的内存区域的一部分。
作为Person引用的实例的VARIABLE p由VALUE传递
该变量实际上不是该类的实例。 该变量是对类实例的引用。 引用按值传递,这意味着您传递引用的副本。 此副本仍指向与原始引用相同的实例。
当人们说.NET中的所有对象都通过Reference传递时,我认为会有一些误解
是的,这绝对是一种误解。 所有参数都按值传递 (除非您使用ref
或out
关键字通过引用传递它们)。 传递引用与通过引用传递不同。
引用是一种值类型,这意味着您作为参数传递的所有内容都是值类型。 您永远不会传递对象实例本身,始终是它的引用。
当你传递一个人时,它正在复制引用 – 不要将它与对象的副本混淆。 换句话说,它正在为同一个对象创建第二个引用,然后传递它。
当您传递ref(使用ref / out关键字)时,它会将相同的引用传递给您在调用者中使用的对象,而不是创建引用的副本。
也许这些例子可以显示引用类型和值类型之间以及通过引用传递和传递值之间的差异:
//Reference type class Foo { public int I { get; set; } } //Value type struct Boo { //I know, that mutable structures are evil, but it only an example public int I { get; set; } } class Program { //Passing reference type by value //We can change reference object (Foo::I can changed), //but not reference itself (f must be the same reference //to the same object) static void ClassByValue1(Foo f) { // f.I++; } //Passing reference type by value //Here I try to change reference itself, //but it doesn't work! static void ClassByValue2(Foo f) { //But we can't change the reference itself f = new Foo { I = fI + 1 }; } //Passing reference typ by reference //Here we can change Foo object //and reference itself (f may reference to another object) static void ClassByReference(ref Foo f) { f = new Foo { I = -1 }; } //Passing value type by value //We can't change Boo object static void StructByValue(Boo b) { b.I++; } //Passing value tye by reference //We can change Boo object static void StructByReference(ref Boo b) { b.I++; } static void Main(string[] args) { Foo f = new Foo { I = 1 }; //Reference object passed by value. //We can change reference object itself, but we can't change reference ClassByValue1(f); Debug.Assert(fI == 2); ClassByValue2(f); //"f" still referenced to the same object! Debug.Assert(fI == 2); ClassByReference(ref f); //Now "f" referenced to newly created object. //Passing by references allow change referenced itself, //not only referenced object Debug.Assert(fI == -1); Boo b = new Boo { I = 1 }; StructByValue(b); //Value type passes by value "b" can't changed! Debug.Assert(bI == 1); StructByReference(ref b); //Value type passed by referenced. //We can change value type object! Debug.Assert(bI == 2); Console.ReadKey(); } }
“按值传递”一词有点误导。
你正在做两件事:
1)将引用类型(Person p)作为参数传递给方法
2)将refence类型变量(Person p2)设置为已存在的变量(Person p)
让我们来看看每个案例。
情况1
你创建了Person p指向内存中的一个位置,让我们调用这个位置x。 当您进入方法AppendWithUnderScore
,您运行以下代码:
p.Name = p.Name + "_";
方法调用创建一个新的局部变量p,它指向内存中的相同位置:x。 因此,如果在方法中修改p, 则会更改p的状态。
但是,在此方法中,如果设置p = null
,则不会使方法外的p无效。 此行为称为“按值传递”
案例2
这种情况与上述情况类似,但略有不同。 当您创建一个新变量p2 = p时,您只是说p2引用p位置处的对象。 所以现在如果你修改p2,你正在修改p,因为它们引用了同一个对象。 如果你现在说p2 = null,那么p现在也将为null。 请注意此行为与方法调用内部行为之间的区别。 这种行为差异概述了调用方法时“按值传递”的方式
规范没有说明在何处分配值类型和对象。 这是一个正确的C#实现,可以说在堆上分配所有东西,还有Atr情况,在堆上分配值而不是你编写的那些。
int i = 4; Func dele =()=>(object)i;
将导致(副本)i在堆上分配,因为编译器将使其成为类的成员,尽管它未被声明为此类。 除此之外你还有很多地方。 并没有一切都没有作为参考传递。 它更接近于说明每个参数都是通过值传递但仍然不完全正确(例如ref或out)。