C#ref的技术含义

时间:2016-11-24 17:00:52

标签: c# pointers ref

我知道c#ref允许您发送指向对象的指针。我只是想知道这对于实际发送到引擎盖下的功能意味着什么。

据我所知,它就像:

DoSomething(ref exampleObject) - >这里指向exampleObject的指针被发送到函数

DoSomething(exampleObject) - >这里将一个指向对象属性的指针数组发送给该函数。

这是对的吗?第二个函数是否也会为值类型创建指针? (看它们是属性,它们的值必须保存在堆上,以保持对象的引用完整性)

更明确的解释: *对象A位于内存位置5 *它具有属性对象B和对象C

现在我打电话给DoSomething(A) - >因为默认情况下我通过指针处理对象,所以我可以传递指针值为5,这样我就可以操作内存地址5的值.C#如何知道发送对象的属性位于哪个内存地址?

现在我打电话给DoSomething(参考A) - >此时我创建了一个指向另一个指针的新指针(比方说6)。所以现在我传入6,这允许我改变内存地址6的值,这很方便地变成5,即对象A的位置。所以现在我可以改变传入的A对象所指向的内存

我不明白的是如何在内存中找到属性?在某个地方必须发送一个指针数组或一个指针数组的指针,对吗?

5 个答案:

答案 0 :(得分:1)

我在第二种情况下的理解

 DoSomething(exampleObject)

exampleObject的地址作为方法DoSomething的参数给出,这意味着在DoSomething中可以重新分配对象的属性(假设引用类型是已使用),但exampleObject的assignmet不会在调用者的范围内更改它,因为exampleObject的地址在堆栈上被修改,而不是在调用者的范围内。另一方面,在第一种情况下

DoSomething(ref exampleObject)

指向exampleObject地址的指针作为DoSomething的参数,这意味着除了更改exampleObject的属性之外,对exampleObject本身的引用可以重新分配。因此,可以交换exampleObject,这种效果在调用者的范围内也是可见的。

答案 1 :(得分:1)

没有。如果你从c ++进入c#,你可以将ref想象成c ++中的引用:

void foo(A*& a)
{ 
     // Here you can change object's state
     // Also you can change value of pointer
     // Caller will have new value of pointer
}
void foo(A* a)
{
    // Here you can change object's state
    // Also you can change value of copy of pointer
    // Caller will have old value of pointer
}

答案 2 :(得分:0)

如果我们知道或期望DoSomething(ref exampleObject)可以执行“计算值或创建新对象并将其返回给我们”,我们可以使用DoSomething。我们知道DoSomething的声明是否也包含ref关键字。

如果在通话之前您的exampleObject包含某个值,或者它指向某个对象(甚至是空),那么在通话之后可能包含一个新值,或可能指向新对象,或者可能已更改为null。

ref没有确定性义务,只有如果 DoSomething决定exampleObject应该更改,然后我们将获得新值或对象(或null)。否则我们会保留以前的东西。

对于值类型(数字,bool,结构)和引用类型(对象)都是如此,这就是为什么我一直说“新值或新对象或null”。

<强> 编辑:
我故意避免提及指针。除非你做的是低级别的东西,否则在C#/ .NET中你几乎不需要知道指针。通常了解管理它们的概念和规则就足够了。

答案 3 :(得分:0)

我总是比较巫毒娃娃的指针 - 无论你对伏都教娃娃做什么,都会影响巫毒娃娃的人和#34;点&#34;到。

所以C#引用就像一个巫毒娃娃 - 它附着在一个物体上,你可以通过消耗巫毒娃娃来影响物体。

但是,如果我突然想要巫毒娃娃指向不同于以前指向的人,会发生什么?使用巫毒娃娃会影响它指向的人 - 而不是巫毒娃娃本身。

解决方案是什么?创造另一个巫毒娃娃 - 到原始巫毒娃娃!通过这种方式,我可以将原始玩偶弄得一团糟,而不仅仅是它所指向的人。

因此C#中的ref是对引用的引用 - 它允许您修改引用,而不仅仅是它指向的对象。

在幕后,引用只是一个数字,表示对象在RAM上的位置。 ref引用是另一个数字,但它表示原始引用位于RAM的哪个位置,而不是它指向的对象位于何处。

答案 4 :(得分:0)

不,不正确。

ref给表带来的是引用语义。

当您调用函数foo(T arg) - 时,无论T是值类型还是引用类型(我都不能强调这一点) - foo获取T的副本。

值类型和引用类型之间的区别在于值类型的名称引用了有趣的东西,而引用类型的名称引用了引用(或指针)有趣的东西。因此,当您复制值类型的对象时,您会获得有趣内容的副本,以及何时复制所述有趣内容的引用。

我在Google上找到的一些图片,而不是我自己绘制的图片:

enter image description here

图像中的副本基本上是在你正在谈论的foo(T arg)时发生的事情。在这两种情况下,我们arg都包含传递给foo的内容的副本。如果Tint,则表示您感兴趣的数字。如果TCircle,则arg为“地址” 1 传递给foo的参数包含。我们可以谈论箭头,而不是谈论指针或参考。引用类型的变量包含有趣内容的箭头,而值类型的变量包含内容本身。

以下代码证明了这一点:

using System;
class MainClass {
  public static void Main (string[] args) {
    Foo foo1 = new Foo();
    foo1.i = 1;
    Console.WriteLine("Main(): foo1.i: {0}", foo1.i);

    doStuffToFoo(foo1, 5);
    Console.WriteLine("Main(): foo1.i: {0}", foo1.i);

    Console.WriteLine();

    Bar bar1;
    bar1.j = 1;
    Console.WriteLine("Main(): bar1.j: {0}", bar1.j);

    doStuffToBar(bar1, 5);
    Console.WriteLine("Main(): bar1.j: {0}", bar1.j);
  }

  static void doStuffToFoo(Foo aFoo, int aInt) {
    aFoo.i = aInt;
    aFoo = new Foo();
    aFoo.i = 123;
    Console.WriteLine("doStuffToFoo: aFoo.i: {0}", aFoo.i);
  }

  static void doStuffToBar(Bar aBar, int aInt) {
    aBar.j = aInt;
    aBar = new Bar();
    aBar.j = 123;
    Console.WriteLine("doStuffToBar: aBar.j: {0}", aBar.j);
  }
}

class Foo {
  public int i;
}

struct Bar {
  public int j;
}

在尝试或阅读之前,看看你是否理解它的印刷品。

aFoo内的变量doStuffToFoofoo1副本。干净利落。 aFoofoo1是不同的变量(存储在堆栈的不同位置 - foo1的堆栈框架中MainaFoo中的doStuffToFoo的堆栈帧 - 假设有一个堆栈,因为the stack is an implementation detail),它们都包含指向同一Foo对象的箭头。

当您引用这两个变量的字段时,它们通过各自的箭头访问相同的Foo对象。但是,当您将new Foo()分配给aFoo时,它只会更改aFoo中的箭头以指向新的Foo对象。它对第一个Foo对象的其他箭头没有任何作用,安全地存储在foo1变量中,并且doStuffToFoo中的代码无法访问。这就是为什么你不会在Main中看到“123”。

所有这一切都将我们带到refref T v表示v不是T类型的新变量(如果T是值类型,则包含一些有趣的内容;如果Tv,则包含一些有趣内容的箭头参考类型)。相反,/***** inside Main: *****/ Foo foo2 = new Foo(); Foo foo3 = foo2; foo2.i = 1; Console.WriteLine("Main(): foo2.i: {0}", foo2.i); Console.WriteLine("Main(): foo3.i: {0}", foo3.i); doStuffToFooRef(ref foo2, 5); Console.WriteLine("Main(): foo2.i: {0}", foo2.i); Console.WriteLine("Main(): foo3.i: {0}", foo3.i); /***** until here *****/ static void doStuffToFooRef(ref Foo aFoo, int aInt) { aFoo.i = aInt; aFoo = new Foo(); aFoo.i = 123; Console.WriteLine("doStuffToFooRef: aFoo.i: {0}", aFoo.i); } 别名,以及作为参数传递的内容。这是其他变量的第二个名称

如果您将以下代码添加到上面的示例中,您将看到它有效。

aFoo

现在foo2aFoo的另一个名称。当您分配到“foo2”时,您将分配给“doStuffToFooRef”,因为这两个名称都指的是同一个变量(存储位置)。这就是i中的任务期间发生的事情:

enter image description here

ref仍然为零,因为它是在我们放置123之前。)

在此处试试:https://repl.it/E80a

C#ref SomeReferenceType x就像这方面的C ++引用一样,АлександрЛысенко在他的回答中写道。虽然从技术上讲,ref 可能是“指向指针的指针”,正如一些人所说的那样,这并没有抓住ref的真正含义,并且详细说明了不一定总是如此(它可能会被优化)。这意味着添加ref会添加指针。那么引用类型的ref-of-a-ref是指向指针的指针吗?每个额外的{{1}}是否会增加一个间接级别?

1 为了讨论的目的,我们不关心它是否真的是一个地址或其他东西。由于对象可能在内存中移动,我们知道它可能不是主机/操作系统的虚拟内存地址,但它是广义上的地址。
有些区分了引用和指针,后者是数字内存地址,但是其他人也不害怕将这些东西称为指针,因为它们指向对象。例如,Java语言规范(§4.3.1)明确地说:

  

参考值(通常只是引用)是指向这些对象的指针,以及一个特殊的空引用,它指的是没有对象。

他们强调“引用” - 他们引入的新术语 - 但是在他们看来,对于读者来说,指针是显而易见的,并且对象也不是不可移动的AFAIK。