• C#参数的按值传递和按引用传递

    一个方法可以包括 0 或多个参数。在方法前面括号中规定的参数列表称为形参,而传递进方法的参数称为实参。

    C# 的默认方式是按值传递(pass by value),若传递对象是值类型,则按值传递之后,传递进方法的不过是值的副本而已,方法外部的对象不受影响。

    按引用传递(pass by reference)之后,传递进方法的是值类型的地址,方法外部的对象会受影响。

    实际上,按引用传递是按值传递的一种特殊情况(引用是地址,地址也是值)。

    例如一个试图调换 a 和 b 值的 Swap 函数,如果其签名为 void Swap(int a, int b),则如果在某处调用该函数,调用完毕后,a 和 b 的值是不会变的。

    因为,传入时会自动复制两个 int 副本,函数内部的任何操作和 a,b 都没有关系。

    如果其签名是 void Swap(ref int a, ref int b),,则改为传入 a 和 b 的地址,不会发生复制,调用该函数将确实会调换 a 和 b 的值。

    如果传递对象是引用类型,则无论是普通的传递还是加上 ref 和 out 关键字,都会更改方法外部的对象。

    加不加 ref 和 out 关键字的区别在于是否可以把引用重新赋值给一个新的对象(即更改传入的地址本身)。

    ref 和 out 的区别是,ref 需要事先赋值,而 out 必须在方法返回之前赋值。

    另外,一个方法可以有多个由 ref 或 out 修饰的输入变量,这间接地令方法有了多个返回值。

    如果两个方法仅仅在 ref 和 out 上有区别(相同名称,相同输岀,相同输入),则在编译器看来他们是相同的方法。

    特殊情况:如果传递的对象是字符串,则其行为类似值类型(字符串的值不改变)。要 传递引用,必须要加 ref 关键字才可以。

    例如 void Swap(string a, string b),调用完毕后,外 部字符串是不会被交换的。

    为了验证引用类型的按引用传递,我们来看下面的代码:

    public class AClass
    {
        public int a;
        public string b;
        public static void ChangeValue(AClass a)
        {
            a.a = 111;
            var b = new AClass();
            b.a = 222;
            a = b;
        }
        public static void ChangeValueRef(ref AClass a)
        {
            a.a = 888;
            var b = new AClass();
            b.a = 999;
            a = b;
        }
    }

    当我们调用时:

    static void Main(string[] args)
    {
        var a = new AClass();
        a.a = 1;
        AClass.ChangeValue(a);
        Console.WriteLine(a.a);
        AClass.ChangeValueRef(ref a);
        Console.WriteLine(a.a);
        Console.ReadKey();
    }

    会输出 111 和 999。我们发现,按值传递时,引用类型栈上的地址不能改变;而按引用传递时,可以改变栈上的地址,指向一个新的堆上的对象。

    在看完上面的例子之后,理解字符串的行为可能就容易了一些。看下面的代码:

    static void Main(string[] args)
    {
        var s = "test";
        Change(s);
        Console.WriteLine(s);
        Console.WriteLine(Change2(s));
    
        ChangeRef(res s);
        COnsole.ReadKey();
    }
    public static void Change(string s)
    {
        s += "1";
    }
    public static void Change2(string s)
    {
        return s += "2";
    }
    public static void ChangeRef(ref string s)
    {
        s += "3";
    }

    当调用 Change 方法之后,字符串池中有两个字符串,分别为 test 和 test 1(大部分情况下,字符串操作每次都产生新的字符串)。

    但是,由于没有更改 s 本身引用的指向(令 s 等于它),所以 test1 实际上没有引用指向它,方法结束之后,它就变成了垃圾。

    此时,外部的 s 仍然指向 test,所以,s 的值不发生变化。这和普通的引用类型按值传递不同,因为,普通的引用类型没有不变性,不会在操作时产生新的对象。

    Change2 方法则返回一个值,所以打印岀的值发生了变化,s 现在指向一个新的值。此时,内存中又增加了一个字符串 test2。

    Change3 方法传递引用,所以可以把字符串 s 赋值给一个新的对象 test3。这和普通的引用类型按引用传递相同。

更多...

加载中...