关键字 ref 如何影响内存管理和垃圾收集?
How does the keyword `ref` affect memory management and garbage collection?
我是 C# 的新手,我一直在摆弄 'ref'、'out' 和指针,我有一个关于 'ref' 如何工作的一般性问题,尤其是在使用对象时而不是原始类型。说这是我的方法:
public void foo(ref Point p) {
p.set(1,1); // the x/y values are updated without constructing a new Point
}
和类似的方法:
public void bar(Point p) {
p.set(1,1); // the x/y values are updated without constructing a new Point
}
编辑:点在两种情况下都是class
两者都有效,但哪一种比另一种更划算?我知道在 C++ 中,如果你传入一个指针,你只给出了内存地址;根据我对 C# 的理解,由于自动垃圾收集,您不能将 Object* 传递到方法中。 'ref' 是否将对象固定到某个位置?此外,如果将对象传递给方法,如上面的 'bar',它传递的是对象的副本还是传递 pointer/reference?
澄清:在我的书中,它确实说如果你想要一个方法来更新一个原语,比如int
,你需要使用ref
(out
如果没有初始化)或者一个*
。我问的是对象是否也是如此,如果将对象作为参数而不是 ref
传递给对象成本更高。
快速区分 class 与结构:
A class is a reference type. When an object of the class is created,
the variable to which the object is assigned holds only a reference to
that memory. When the object reference is assigned to a new variable,
the new variable refers to the original object. Changes made through
one variable are reflected in the other variable because they both
refer to the same data.
A struct is a value type. When a struct is
created, the variable to which the struct is assigned holds the
struct's actual data. When the struct is assigned to a new variable,
it is copied. The new variable and the original variable therefore
contain two separate copies of the same data. Changes made to one copy
do not affect the other copy.
https://msdn.microsoft.com/en-us/library/ms173109.aspx
你的例子很棘手,因为在 C# 中 Point 是一个不可变的结构,而不是一个对象。
希望这个例子能帮助展示结构和对象在有和没有 ref 的情况下会发生什么。
public static void StructTest()
{
var fooStruct = new MyStruct();
var barStruct = new MyStruct();
Console.WriteLine(fooStruct.Value); // prints 0
Console.WriteLine(barStruct.Value); // prints 0
fooStruct(ref fooStruct);
barStruct(barStruct);
// Struct value only changes when passed by reference.
Console.WriteLine(fooStruct.Value); // prints 1
Console.WriteLine(barStruct.Value); // prints 0
}
public void fooStruct(ref MyStruct m)
{
m.Value++;
}
public void barStruct(MyStruct m)
{
m.Value++;
}
public static void ObjectTest()
{
var fooObject = new MyObject();
var barObject = new MyObject();
Console.WriteLine(fooObject.Value); // prints 0
Console.WriteLine(barObject.Value); // prints 0
fooObject(ref fooObject);
barObject(barObject);
// Objects are automatically passed by reference. No difference.
Console.WriteLine(fooObject.Value); // prints 1
Console.WriteLine(barObject.Value); // prints 1
fooSetObjectToNull(ref fooObject);
barSetObjectToNull(barObject);
// Reference is actually a pointer to the variable that holds a reference to the object.
Console.WriteLine(fooObject == null); // prints true
Console.WriteLine(barObject == null); // prints false
}
public void fooObject(ref MyObject m)
{
m.Value++;
}
public void barObject(ref MyObject m)
{
m.Value++;
}
public void fooSetObjectToNull(ref MyObject m)
{
m = null;
}
public void barSetObjectToNull(MyObject m)
{
m = null;
}
其实class是引用类型,就是说引用类型的变量持有的是对其数据的引用,而不是像值类型那样直接持有数据。
当您将引用类型的变量作为方法参数传递时,它会传递对该数据的引用,而不是数据本身。因此,如果更新对象的某些属性,则更新会反映在原始变量中,除非您重新分配参数。
来自 MSDN 的示例:
class PassingRefByVal
{
static void Change(int[] pArray)
{
pArray[0] = 888; // This change affects the original element.
pArray = new int[5] {-3, -1, -2, -3, -4}; // This change is local.
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
static void Main()
{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr [0]);
Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr [0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: 888
*/
使用 ref 关键字传递引用类型的变量将反映对原始变量的任何更改,即使您重新设置参数也是如此。
来自 MSDN 的示例:
class PassingRefByRef
{
static void Change(ref int[] pArray)
{
// Both of the following changes will affect the original variables:
pArray[0] = 888;
pArray = new int[5] {-3, -1, -2, -3, -4};
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
static void Main()
{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);
Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: -3
*/
MSDN 文档。
- 如果您的类型是结构,
ref
大致等同于指向该结构的指针。这里没有创建新实例。不同之处在于(与不使用 ref
传递它)是您现在可以改变该变量中包含的原始结构实例。
- 如果您的类型是 class,
ref
只需再添加一层间接寻址。这里也没有创建新实例。不同之处在于(与不使用 ref
传递它)是您现在可以用其他东西完全替换(而不仅仅是变异)该变量引用的原始 class 实例。
由于在这两种情况下都没有创建新实例,垃圾收集器可能不会以任何重要方式关心这个。
我是 C# 的新手,我一直在摆弄 'ref'、'out' 和指针,我有一个关于 'ref' 如何工作的一般性问题,尤其是在使用对象时而不是原始类型。说这是我的方法:
public void foo(ref Point p) {
p.set(1,1); // the x/y values are updated without constructing a new Point
}
和类似的方法:
public void bar(Point p) {
p.set(1,1); // the x/y values are updated without constructing a new Point
}
编辑:点在两种情况下都是class
两者都有效,但哪一种比另一种更划算?我知道在 C++ 中,如果你传入一个指针,你只给出了内存地址;根据我对 C# 的理解,由于自动垃圾收集,您不能将 Object* 传递到方法中。 'ref' 是否将对象固定到某个位置?此外,如果将对象传递给方法,如上面的 'bar',它传递的是对象的副本还是传递 pointer/reference?
澄清:在我的书中,它确实说如果你想要一个方法来更新一个原语,比如int
,你需要使用ref
(out
如果没有初始化)或者一个*
。我问的是对象是否也是如此,如果将对象作为参数而不是 ref
传递给对象成本更高。
快速区分 class 与结构:
A class is a reference type. When an object of the class is created, the variable to which the object is assigned holds only a reference to that memory. When the object reference is assigned to a new variable, the new variable refers to the original object. Changes made through one variable are reflected in the other variable because they both refer to the same data.
A struct is a value type. When a struct is created, the variable to which the struct is assigned holds the struct's actual data. When the struct is assigned to a new variable, it is copied. The new variable and the original variable therefore contain two separate copies of the same data. Changes made to one copy do not affect the other copy.
https://msdn.microsoft.com/en-us/library/ms173109.aspx
你的例子很棘手,因为在 C# 中 Point 是一个不可变的结构,而不是一个对象。
希望这个例子能帮助展示结构和对象在有和没有 ref 的情况下会发生什么。
public static void StructTest()
{
var fooStruct = new MyStruct();
var barStruct = new MyStruct();
Console.WriteLine(fooStruct.Value); // prints 0
Console.WriteLine(barStruct.Value); // prints 0
fooStruct(ref fooStruct);
barStruct(barStruct);
// Struct value only changes when passed by reference.
Console.WriteLine(fooStruct.Value); // prints 1
Console.WriteLine(barStruct.Value); // prints 0
}
public void fooStruct(ref MyStruct m)
{
m.Value++;
}
public void barStruct(MyStruct m)
{
m.Value++;
}
public static void ObjectTest()
{
var fooObject = new MyObject();
var barObject = new MyObject();
Console.WriteLine(fooObject.Value); // prints 0
Console.WriteLine(barObject.Value); // prints 0
fooObject(ref fooObject);
barObject(barObject);
// Objects are automatically passed by reference. No difference.
Console.WriteLine(fooObject.Value); // prints 1
Console.WriteLine(barObject.Value); // prints 1
fooSetObjectToNull(ref fooObject);
barSetObjectToNull(barObject);
// Reference is actually a pointer to the variable that holds a reference to the object.
Console.WriteLine(fooObject == null); // prints true
Console.WriteLine(barObject == null); // prints false
}
public void fooObject(ref MyObject m)
{
m.Value++;
}
public void barObject(ref MyObject m)
{
m.Value++;
}
public void fooSetObjectToNull(ref MyObject m)
{
m = null;
}
public void barSetObjectToNull(MyObject m)
{
m = null;
}
其实class是引用类型,就是说引用类型的变量持有的是对其数据的引用,而不是像值类型那样直接持有数据。
当您将引用类型的变量作为方法参数传递时,它会传递对该数据的引用,而不是数据本身。因此,如果更新对象的某些属性,则更新会反映在原始变量中,除非您重新分配参数。
来自 MSDN 的示例:
class PassingRefByVal
{
static void Change(int[] pArray)
{
pArray[0] = 888; // This change affects the original element.
pArray = new int[5] {-3, -1, -2, -3, -4}; // This change is local.
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
static void Main()
{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr [0]);
Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr [0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: 888
*/
使用 ref 关键字传递引用类型的变量将反映对原始变量的任何更改,即使您重新设置参数也是如此。
来自 MSDN 的示例:
class PassingRefByRef
{
static void Change(ref int[] pArray)
{
// Both of the following changes will affect the original variables:
pArray[0] = 888;
pArray = new int[5] {-3, -1, -2, -3, -4};
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
static void Main()
{
int[] arr = {1, 4, 5};
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);
Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: -3
*/
MSDN 文档。
- 如果您的类型是结构,
ref
大致等同于指向该结构的指针。这里没有创建新实例。不同之处在于(与不使用ref
传递它)是您现在可以改变该变量中包含的原始结构实例。 - 如果您的类型是 class,
ref
只需再添加一层间接寻址。这里也没有创建新实例。不同之处在于(与不使用ref
传递它)是您现在可以用其他东西完全替换(而不仅仅是变异)该变量引用的原始 class 实例。
由于在这两种情况下都没有创建新实例,垃圾收集器可能不会以任何重要方式关心这个。