C#中ref、out、in关键字的用法是什么?

在 C# 中,大多数方法可以有零个或多个参数,这些参数定义必须提供给方法的数据。任何调用该方法的代码都必须将数据(称为参数)传递给该方法。一个方法将它的输入声明为参数,它们是通过以参数的形式调用代码来提供的。

例如,考虑以下方法和后续方法调用。

static void Greet(string greeting){
   Console.WriteLine(greeting);
}
...
Greet("Hello");

在上面的例子中,greeting 是Greet()方法的参数,“Hello”是传递给方法的参数。

当您调用一个方法并传递参数时,它们是按值传递的,这意味着在传递给该方法时会创建该值的副本。对方法内部参数所做的任何更改都不会反映在原始变量上。

using System;
int number = 8;
Increase(number);
Console.WriteLine(number); // 打印 8
static void Increase(int num){
   num = num + 1;
   Console.WriteLine(num); // 打印 9
}

当您传递引用类型变量时,例如对象,C# 仍然按值复制引用,因为变量保存的是引用,而不是实际对象。因此,即使传递了引用变量的副本,它们也指向内存中的同一个对象。因此,方法内部的变量对对象所做的任何更改对方法外部的变量都是可见的。

using System;
var john = new User{
   Name = "John",
   Salary = 50000
};
Promote(john);
Console.WriteLine(john.Salary); // 打印 60000
static void Promote(User u){
   u.Salary += 10000;
   Console.WriteLine(u.Salary); // 打印 60000
}

但是,如果您在方法内部更改变量的值本身,则该更改不会反映在方法外部,因为仅更改了副本,而不是实际对象。例如,我们可以在方法内部将参数赋值为 null,而实际变量将保持不变。

using System;
var john = new User{
   Name = "John",
   Salary = 50000
};
Promote(john);
Console.WriteLine(john.Salary); // 打印 50000
static void Promote(User u){
   u = null;
}

C# 允许使用三种不同的修饰符关键字,您可以使用它们来控制方法的参数。

ref 修饰符

C# 允许您使用ref修饰符通过引用传递参数。传递的变量是属于引用类型还是值类型并不重要。参数和自变量都将引用相同的内存位置。

在下面的示例中,将参数 u 设置为 null 也会使 john 变量为 null,从而导致 null 引用异常。

例子

using System;
var john = new User{
   Name = "John",
   Salary = 50000
};
Promote(ref john);
Console.WriteLine(john.Salary); // 抛出 System.NullReferenceException
static void Promote(ref User u){
   u = null;
}

使用 ref 修饰符传递的参数必须在传递之前进行初始化。

输出修饰符

它类似于 ref 修饰符,除了

  • 参数在进入函数之前不必初始化

  • 参数在从函数中出来之前必须被初始化(分配给)。

在下面的示例中,Hire 函数初始化一个通过 out 修饰符传递给它的新用户对象。请注意,变量是在调用 Hire 方法时动态声明的变量 john。

using System;
Hire(out User john);
Console.WriteLine(john.Salary); // 打印 50000
static void Hire(out User u){
   u = new User{
      Name = "John",
      Salary = 50000
   };
}

ref修饰符一样,由out修饰符标记的变量也是通过引用传递的。

通常使用out变量从函数中获取多个返回值,如下所示 -

using System;
var john = new User{
   Name = "John",
   Salary = 50000
};
bool shouldPromote = Raise(john.Salary, out double hike);
Console.WriteLine(shouldPromote); // 真的
Console.WriteLine($"Hike Amount = {hike}"); // 打印 5000
static bool Raise(int salary, out double hike){
   hike = salary * 0.1;
   return hike < 7000;
}

in 修饰符

它类似于refout参数,不同之处在于接受in参数值的方法不能修改该参数。如果尝试,C# 编译器会生成编译时错误。

参数保存来自复制大值类型的存储器的参数变量,同时防止物体意外修改的编译器。这使得它们在将大值类型传递给方法时非常有用。

var point = new Coord();
Verify(point);
static void Verify(in Coord c){
   // 错误:无法分配给变量“in Coord”,因为它是只读变量
   c = new Coord();
}
struct Coord{
   public int X;
   public int Y;
}

示例

using System;
class Program{
   static void Main(){
      int number = 8;
      Increase(number);
      Console.WriteLine(number); // 打印 8
      var john = new User { Name = "John", Salary = 50000 };
      Promote(john);
      Console.WriteLine(john.Salary); // 打印 60000
      Leave(john);
      Console.WriteLine(john.Salary); // 打印 60000

      LeaveByRef(ref john);
      Console.WriteLine(john?.Salary); // 什么都不打印

      User dave;
      Hire(out dave);
      Console.WriteLine(dave.Salary); // 打印 50000
   }
   static void Increase(int num){
      num = num + 1;
      Console.WriteLine(num); // 打印 9
   }
   static void Promote(User u){
     u.Salary+= 10000;
      Console.WriteLine(u.Salary); // 打印 60000
   }
   static void Leave(User u){
      u = null;
   }
   static void LeaveByRef(ref User u){
      u = null;
   }
   static void Hire(out User u){
      u = new User{
         Name = "John",
         Salary = 50000
      };  
   }
}
class User{
   public string Name { get; set; }
   public int Salary { get; set; }
}
输出结果
9
8
60000
60000
60000
50000