ser*_*0ne 9 c# value-type reference-type pass-by-reference pass-by-value
请考虑以下代码(我有意将 MyPoint编写为此示例的引用类型)
public class MyPoint
{
public int x;
public int y;
}
Run Code Online (Sandbox Code Playgroud)
它是普遍公认的(至少在C#中)当你通过引用传递时,该方法包含对被操作对象的引用,而当你通过值传递时,该方法复制被操纵的值,因此全局范围中的值是不受影响.
例:
void Replace<T>(T a, T b)
{
a = b;
}
int a = 1;
int b = 2;
Replace<int>(a, b);
// a and b remain unaffected in global scope since a and b are value types.
Run Code Online (Sandbox Code Playgroud)
这是我的问题; MyPoint是引用类型,所以我希望在相同的操作Point,以取代a与b在全球范围内.
例:
MyPoint a = new MyPoint { x = 1, y = 2 };
MyPoint b = new MyPoint { x = 3, y = 4 };
Replace<MyPoint>(a, b);
// a and b remain unaffected in global scope since a and b...ummm!?
Run Code Online (Sandbox Code Playgroud)
我期待a并b在记忆中指出相同的参考...有人可以澄清我哪里出错了吗?
Stu*_*tLC 25
Re:OP的断言
它是普遍公认的(至少在C#中)当你通过引用传递时,该方法包含对被操作对象的引用,而当你通过值传递时,该方法复制被操纵的值...
TL; DR
除非使用ref或out关键字传递变量,否则C#会按值将变量传递给方法,而不管变量是值类型还是引用类型.
如果通过引用传递,则被调用函数可以更改变量的地址(即更改原始调用函数的变量的赋值).
如果变量按值传递:
由于这一切都相当复杂,我建议尽可能避免通过引用传递(相反,使用复合类或结构作为返回类型,或使用元组)
如果可以的话,可以通过永远不改变(变异)传递给方法的对象的字段和属性来避免许多错误.不可变的属性将阻止变化.
详细地
问题是有两个不同的概念:
除非您通过引用显式传递(任何)变量,否则通过使用out或ref关键字,参数将通过C#中的值传递,而不管变量是值类型还是引用类型.
传递值类型(例如int,float或类似结构DateTime)时,被调用函数获取整个值类型的副本(通过堆栈).
退出被调用函数时,对值类型的任何更改以及对副本的任何属性/字段的任何更改都将丢失.
但是,对于引用类型(例如类的自定义类MyPoint),它是reference在堆栈上复制和传递的相同的共享对象实例.
这意味着:
xor的任何更改y)这里发生了什么:
void Replace<T>(T a, T b) // Both a and b are passed by value
{
a = b; // reassignment is localized to method `Replace`
}
Run Code Online (Sandbox Code Playgroud)
对于引用类型T,表示将对象的局部变量(堆栈)引用a重新分配给本地堆栈引用b.此重新分配仅对此函数是本地的 - 只要作用域离开此函数,重新分配就会丢失.
如果您真的想要替换调用者的引用,则需要更改签名,如下所示:
void Replace<T>(ref T a, T b) // a is passed by reference
{
a = b; // a is reassigned, and is also visible to the calling function
}
Run Code Online (Sandbox Code Playgroud)
这改变了对引用调用的调用 - 实际上我们将调用者的变量的地址传递给函数,然后允许被调用的方法改变调用方法的变量.
但是,现在:
Tuple或custom class或struct包含所有这些返回变量.编辑
这两个图可能有助于解释.
按值传递(参考类型):
在您的第一个实例(Replace<T>(T a,T b))中,a并按b值传递.对于引用类型,这意味着引用被复制到堆栈并传递给被调用的函数.
main)分配两个MyPoint在托管堆上对象(我称这些point1和point2),然后分配两个本地变量引用a和b,以引用点,分别(淡蓝色箭头):MyPoint a = new MyPoint { x = 1, y = 2 }; // point1
MyPoint b = new MyPoint { x = 3, y = 4 }; // point2
Run Code Online (Sandbox Code Playgroud)
然后调用Replace<Point>(a, b)将两个引用的副本推送到堆栈(红色箭头).方法Replace认为这些作为两个参数也被命名a和b,这仍然指向point1和point2,分别为(橙色箭头).
赋值,a = b;然后更改Replace方法的a局部变量,使得a现在指向与b(ie point2)引用的同一对象.但请注意,此更改仅适用于Replace的本地(堆栈)变量,此更改仅会影响Replace(深蓝色线条)中的后续代码.它不会以任何方式影响调用函数的变量引用,NOR会改变堆上的point1和point2对象.
通过引用传递:
但是,如果我们将调用Replace<T>(ref T a, T b)更改main为然后更改为通过a引用传递,即Replace(ref a, b):
和以前一样,堆上分配了两个点对象.
现在,当Replace(ref a, b)调用时,在调用期间仍然复制mains引用b(指向point2),a现在通过引用传递,这意味着传递给main的a变量的"地址" Replace.
现在当a = b作出任务时......
它是调用函数main的a变量引用,现在更新为引用point2.通过重新分配进行了更改,以a双方现在看到的main和Replace.现在没有提及point1
引用该对象的所有代码都可以看到对(堆分配的)对象实例的更改
在上面的两个场景中,实际上没有对堆对象进行任何更改,point1并且point2只有传递和重新分配的局部变量引用.
但是,如果任何变化实际上对堆中的对象做point1和point2,然后对这些对象的所有变量的引用会看到这些变化.
所以,例如:
void main()
{
MyPoint a = new MyPoint { x = 1, y = 2 }; // point1
MyPoint b = new MyPoint { x = 3, y = 4 }; // point2
// Passed by value, but the properties x and y are being changed
DoSomething(a, b);
// a and b have been changed!
Assert.AreEqual(53, a.x);
Assert.AreEqual(21, b.y);
}
public void DoSomething(MyPoint a, MyPoint b)
{
a.x = 53;
b.y = 21;
}
Run Code Online (Sandbox Code Playgroud)
现在,当执行返回到main,所有参考文献point1和point2,包括main's变量a和b,现在将"看见"的变化时,他们接下来的读取值x和y观点.您还会注意到,变量a和b仍然按值传递给DoSomething.
对值类型的更改仅影响本地副本
值类型(原语喜欢System.Int32,System.Double和结构喜欢System.DateTime)被分配堆栈,而不是堆上,并且当传入呼叫被逐字拷贝到堆栈.这里的一个区别是被调用函数对值类型字段或属性所做的更改只能由被调用函数在本地观察,因为它只会改变值类型的本地副本.
例如,考虑以下代码和可变结构的实例, System.Drawing.Rectangle
public void SomeFunc(System.Drawing.Rectangle aRectangle)
{
// Only the local SomeFunc copy of aRectangle is changed:
aRectangle.X = 99;
// Passes - the changes last for the scope of the copied variable
Assert.AreEqual(99, aRectangle.X);
} // The copy aRectangle will be lost when the stack is popped.
// Which when called:
var myRectangle = new System.Drawing.Rectangle(10, 10, 20, 20);
// A copy of `myRectangle` is passed on the stack
SomeFunc(myRectangle);
// Test passes - the caller's struct has NOT been modified
Assert.AreEqual(10, myRectangle.X);
Run Code Online (Sandbox Code Playgroud)
上面的内容可能非常令人困惑,并强调了为什么将自己的自定义结构创建为不可变的优良做法.
的ref关键字的工作方式类似于允许值类型变量为通过引用而通过,即,该呼叫方的值的变量类型"地址"被传递到堆栈,和调用者的分配的变量的赋值是现在直接可能的.
C#实际上是按值传递的.你得到了它通过引用传递的幻觉,因为当你传递一个引用类型时,你得到一个引用的副本(引用是通过值传递的).但是,由于您的替换方法正在用另一个引用替换该引用副本,因此它实际上什么都不做(复制的引用立即超出范围).您实际上可以通过添加ref关键字来引用:
void Replace<T>(ref T a, T b)
{
a = b;
}
Run Code Online (Sandbox Code Playgroud)
这将获得你想要的结果,但在实践中有点奇怪.
| 归档时间: |
|
| 查看次数: |
7759 次 |
| 最近记录: |