不使用ref关键字进行按引用传递

8

我不是socket编程方面的老手,所以在分析我在数据库API中发现的代码时,遇到了这段代码。

    public static void WriteInt(int i, NetworkStream bufOutputStream) 
    {
        byte[] buffer = new byte[IntSize];
        WriteInt(i, buffer, 0);
        bufOutputStream.Write(buffer, 0, buffer.Length);
    }

    public static void WriteInt(int i, byte[] byte_array, int pos)
    {

        byte_array[pos] =(byte)( 0xff & (i >> 24)); byte_array[pos+1] = (byte)(0xff & (i >> 16)); byte_array[pos+2] = (byte)(0xff & (i >> 8)); byte_array[pos+3] = (byte)(0xff & i);
    }

我理解位移操作,但不理解当参数中没有ref或没有返回值时,'buffer'变量是如何获得值的。这些位移操作似乎在编辑buffer变量的实际值?


请参见http://stackoverflow.com/questions/2058161/do-you-need-the-ref-or-out-parameter/2058320#2058320。 - Eric Lippert
8个回答

28

你的困惑是非常普遍的。关键是要认识到“引用类型”和“按引用传递”(ref 关键字)是 完全独立 的概念。在这种特定情况下,由于 byte[] 是一个引用类型(所有数组都是引用类型),这意味着当你传递它时对象不会被复制,因此你总是引用同一个对象。

我强烈推荐你阅读 Jon Skeet 的优秀文章:C# 中的参数传递,所有问题都会变得清晰明了...


6
因为数组不是值类型,而是引用类型。堆上的位置引用按值传递。

这并没有真正解决jtzero对ref使用的困惑。 - Sean
1
byte[] 是 System.Array 的子类,它是一个引用类型。 - user1228
@sean 引用类型总是按引用传递,值类型总是按值传递...除非你使用 refout,这就是这些关键字存在的原因。将它们添加到引用参数中就像是一支未削尖的铅笔。 - user1228
5
这是不正确的。不要混淆引用类型和按引用传递...它们是完全独立的。 - Noldorin
@Noldorin: 我觉得这只是措辞上的问题。 - Yuriy Faktorovich

2

我认为一些例子可以向您展示引用类型和值类型之间的区别,以及按引用传递和按值传递之间的区别:

//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 = f.I + 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(f.I == 2);

        ClassByValue2(f);
        //"f" still referenced to the same object!
        Debug.Assert(f.I == 2);

        ClassByReference(ref f);
        //Now "f" referenced to newly created object.
        //Passing by references allow change referenced itself, 
        //not only referenced object
        Debug.Assert(f.I == -1);

        Boo b = new Boo { I = 1 };

        StructByValue(b);
        //Value type passes by value "b" can't changed!
        Debug.Assert(b.I == 1);

        StructByReference(ref b);
        //Value type passed by referenced.
        //We can change value type object!
        Debug.Assert(b.I == 2);

        Console.ReadKey();
    }

}

2
最好的思考方式是考虑变量。变量根据定义是存储位置。在程序中,哪些是存储位置呢?它们包括:
  • 第一个WriteInt的形式参数i和bufOutputStream。
  • 第一个WriteInt的本地变量缓冲区。
  • 缓冲区引用的数组元素(共“IntSize”个)在分配后。
  • 第二个WriteInt的形式参数i、byte_array和pos。
byte_array和buffer都是不同的存储位置。但是byte_array存储位置包含对与buffer相同的数组的引用。因此,buffer [0]和byte_array [0]指向相同的存储位置。
只需考虑存储位置,就可以理解这一切。

1

.Net 中的数组是引用类型。

因此,您的函数通过值接收数组对象的引用。由于仍然只有一个数组实例,函数可以修改该实例并且更改将被调用者看到。

添加 ref 关键字将使函数通过引用接收数组对象的引用,并因此允许函数更改引用以引用不同的数组实例。

换句话说,ref 关键字将允许您编写以下内容:

public static void WriteInt(int i, ref byte[] byte_array, int pos)
{
    byte_array = new byte[0];    //In the caller, the array will now be empty.
}

为了演示:

void SetReference(ref byte[] arrayRef) { arrayRef = new byte[1]; }

void SetValue(byte[] arrayVal) { arrayVal[1] = 42; }

byte[] array = new byte[4];
byte[] sameArray = array;    //sameArray refers to the same instance

sameArray[0] = 77;           //Since it's the same instance, array[4] is also 77.

SetValue(array);             //array[1] is 42.
                             //Since it refers to the same array, sameArray[1] is also 42.

SetReference(ref array);     //sameArray now refers to a new array of length 1.
                             //array still refers to the original array.

1

C#和Java类似,引用类型变量实际上是指针。您始终按值传递,但对于引用类型,该值是对象的位置,而不是对象本身。在引用类型上使用ref关键字是通过引用传递指针,因此对ref参数的赋值将更改传入参数所指向的对象。


0

byte_array 是一个引用类型。


0

就像Yuriy Faktorovich所说,值类型(如int、char、bool等)默认情况下按值传递(除非您指定ref

所有其他类型(数组和对象)默认情况下按引用传递

在您的示例中,如果您更改数组内的值,则它也会反映在方法外部的更改,但您无法重新分配对象。

有关此的完整参考,请参阅MSDN


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接