[C#] 方法-难点(草稿)

局部函数

局部函数示例

点击查看代码
void OuterMethod()
{
    // 这是一个局部函数
    void LocalFunction()
    {
        Console.WriteLine("This is a local function");
    }

    // 调用局部函数
    LocalFunction();
}

点击查看代码
using System;

namespace Simple
{
    class Progrma
    {
       public void MethodWithLocalFunction(int z1)
        {
            int MyLocalFunction(int z1)  //声明局部函数
            {
                return z1 * 5;
            }

            int result = MyLocalFunction(z1);
            Console.WriteLine($"局部函数返回值是:{result}");

        }

        static void Main()
        {
            Progrma myProgram = new Progrma();
            myProgram.MethodWithLocalFunction(5);
        }
    }
}

局部函数的特点

作用域限制:局部函数只能在它们定义的外部方法中使用。它们对外部方法之外的代码不可见。

点击查看代码

//在C#中,局部函数的作用域限制意味着它们只能在定义它们的外部方法内部使用。它们对外部方法之外的代码是不可见的。这种限制<u>确保了局部函数只能在**其定义的上下文**中使用,避免了命名冲突和意外调用</u>。

//假设我们有一个外部方法 OuterMethod,在这个方法内部定义了一个局部函数 LocalFunction:
void OuterMethod()
{
    void LocalFunction()
    {
        Console.WriteLine("This is a local function");
    }

    LocalFunction(); // 在这里调用局部函数
}

/*在这个例子中:

LocalFunction 是定义在 OuterMethod 内部的局部函数。
LocalFunction 只能在 OuterMethod 内部被调用和访问。
LocalFunction 对 OuterMethod 之外的任何代码都是不可见的。
*/

访问外部变量:局部函数可以访问它们所在方法的局部变量和参数。

点击查看代码
void OuterMethod(int x)
{
    int y = 5;

    void LocalFunction()
    {
        Console.WriteLine($"x = {x}, y = {y}");
    }

    LocalFunction();
}

递归调用:局部函数可以进行递归调用,即在函数内部调用自身。

点击查看代码
void OuterMethod()
{
    int Factorial(int n)
    {
        if (n <= 1) return 1;
        return n * Factorial(n - 1);
    }

    Console.WriteLine(Factorial(5)); // 输出 120
}

//在这个例子中,Factorial 是一个局部函数,通过递归计算阶乘。

延迟执行:局部函数可以用于延迟执行某些逻辑,尤其在使用 async 和 await 时非常有用

点击查看代码
async Task OuterMethod()
{
    async Task<int> LocalFunctionAsync(int n)
    {
        await Task.Delay(1000);
        return n * n;
    }

    int result = await LocalFunctionAsync(5);
    Console.WriteLine(result); // 输出 25
}

在这个例子中,LocalFunctionAsync 是一个异步局部函数,计算平方并在等待一秒钟后返回结果。


《C#图解教程 第六章》
方法块内的代码可以调用另一个方法。
如果另一个方法在同一个类内,可以直接使用它的名称,并传入所需要的参数进行调用。
如果另一个方法在不同的类中,必须通过这个类的一个对象实例调用它。
另一个类中的方法必须使用public访问修饰。

同一个类中的方法调用

点击查看代码
class MyClass
{
    void MethodA()
    {
        Console.WriteLine("MethodA is called");
    }

    void MethodB()
    {
        // 在 MethodB 中调用 MethodA
        MethodA(); // 直接调用
    }
}

不同类中的方法调用

点击查看代码
class ClassA
{
    public void MethodA()
    {
        Console.WriteLine("MethodA in ClassA is called");
    }
}

class ClassB
{
    void MethodB()
    {
        // 创建 ClassA 的一个实例
        ClassA instanceOfClassA = new ClassA();
        
        // 使用实例调用 ClassA 中的 MethodA
        instanceOfClassA.MethodA();
    }
}


使用场景
局部函数非常适合以下场景:

封装复杂的逻辑:将复杂的方法拆分为多个小的、可读性更强的局部函数。
避免代码重复:在一个方法中多次使用相同的代码段时,可以将其封装为局部函数。
递归操作:在一个方法中实现递归操作,如树结构的遍历。
简化异步代码:在异步方法中封装异步操作,避免方法变得过于复杂。
局部函数是C#中一个强大且灵活的特性,使代码更模块化和可维护。它们帮助开发者在保持代码结构清晰的同时,实现各种复杂的逻辑。

参数

方法是能从程序中的很多地方调用命名代码单元,它能把一个值返回给调用代码。
1.那如果需要返回多个值呢?
2.能做方法开始执行的时候把数据传入也会有用。

形参

形参是局部变量,它声明在方法的参数列表中,而不是在方法体中。

public void PrintSum(int x,int y){ ... }

  • 因为形参是变量,所以它们有类型和名称,并能被写入和读取。
  • 和方法中的其它局部变量不同,参数在方法体外定义并在方法开始之前初始化(输出参数是例外)
  • 参数在列表中可以有任意数目的形参声明,而且声明必须用逗号隔开。
使用元组返回多个值
//如何返回多个值
//使用元组

public (int, string, bool) GetMultipleValues()
{
    return (1, "example", true);
}

// 调用该方法并获取返回值
var result = GetMultipleValues();
int number = result.Item1;
string text = result.Item2;
bool flag = result.Item3;


使用out返回多个值

public void GetMultipleValues(out int number, out string text, out bool flag)
{
    number = 1;
    text = "example";
    flag = true;
}

// 调用该方法并获取返回值
GetMultipleValues(out int num, out string txt, out bool flg);


点击查看代码
using System;
using static System.Net.Mime.MediaTypeNames;

namespace Simple
{
    class Progrma
    {
              public void GetMultipleValues(out int number, out string text, out bool flag)
        {
            number = 1;
            text = "example";
            flag = true;
        }

        static void Main()
        {
            Progrma program = new Progrma();
            //myProgram.MethodWithLocalFunction(5);

            program.GetMultipleValues(out int number, out string text, out bool flag);

            // 打印返回值
            Console.WriteLine($"Number: {number}");
            Console.WriteLine($"Text: {text}");
            Console.WriteLine($"Flag: {flag}");
        }
    }
}


out的作用。
1.指示参数是输出参数:out参数用于从方法中返回数据,而不仅仅是传递数据到方法。调用方法后,通过out参数返回的值会被传递回调用者。
2.确保参数在方法中被赋值:方法内部必须为所有的out参数赋值,否则编译器会报错。这保证了当方法返回时,out参数始终包含有效的数据。
3.不需要初始化:在调用方法时,不需要为out参数赋初值,因为它们在方法内部会被赋值。

实参

当代码调用一个方法时,形参的值必须在方法的代码开始执行之前初始化。
用于初始化形参的表达式是实参(argument)。

  • 实参位于方法调用的列表中
  • 每一个实参必须有对应形参的类型相匹配。或者编译器能把实参隐式转换为那个类型。

位置参数
实参的数量和形参的数量一致,并且每个实参的类型,必须和所对应的形参类型一致。

值参数

参数有几种,各自以略微不同的方式从方法传入或传出数据。
默认类型,是值参数

当使用值参数时,系统使用复制实参到形参的方式把数据传递过去。
方法被调用时,系统执行如下操作:

  • 在栈中为形参分配空间
  • 将实参的值复制给形参

值参数的值不一定是变量,可以是任何能计算成相应数据类型的表达式。

点击查看代码
        static void Main()
        {
            float k = 5.1F;  // 在Main方法中定义变量k
            float j = 2.6F;  // 在Main方法中定义变量j

            float fValue1 = Func1(k);
            float fValue2 = Func1((k + j) / 3);  //参数是计算成fload的表达式

            Console.WriteLine($"fValue1: {fValue1}");
            Console.WriteLine($"fValue2: {fValue2}");
        }

实参与形参在方法执行的不同阶段的值:
示例:

点击查看代码
    class MyClass
    {
        public int Val = 20;    //初始化字段为20
    }
    class Progrma
    {
        static void MyMethod(MyClass f1,int f2)
        {
            f1.Val = f1.Val+5;  //参数的字段加5
            f2 = f2 + 5;

            Console.WriteLine($"f1.Val:{f1.Val},f2:{f2}");
        }

        static void Main()
        {
            MyClass a1 = new MyClass();
            int a2 = 10;

            MyMethod(a1,a2);
            Console.WriteLine($"a1.Val:{a1.Val},f2:{a2}");
        }
    }

1 方法被调用前,用作实参的变量a2就已经在栈里了。
2 方法开始时,系统在栈中为形参分配空间,并从实参复制值。

  • 因为a1是引用类型,所以引用被复制,结果实参和形参都引用堆中同一个对象。
  • 因为a2是值类型,所以值被复制,产生了一个独立的数据项。
    3 在方法的结尾,f2 和对象f1的字段都被加上了5.
  • 方法执行后,形参从栈中弹出
  • a2,值类型,它的值不受方法行为的影响。
  • a1,引用类型,但它的值被方法和行为改变了。

引用参数

第二种参数类型为引用参数

Chatgpt:
引用参数(Reference Parameters)是 C# 中的一种参数传递方式,允许方法修改调用方提供的变量的值。在 C# 中,参数传递默认是按值传递的,这意味着方法接收到的是变量的副本,而不是原始变量本身。但是,使用引用参数可以让方法直接访问和修改调用方提供的变量。

在 C# 中,引用参数使用 ref 或 out 关键字声明。主要区别在于 ref 关键字要求调用方在传递参数时必须初始化,而 out 关键字则不要求,但方法必须在使用 out 参数之前将其赋值。

以下是引用参数的一些关键点:

1 使用 ref 关键字: 使用 ref 关键字声明的参数必须在方法调用时初始化。这表示调用方必须在传递参数之前将其初始化,方法内的任何更改都将反映在原始变量上。

点击查看代码
void ModifyValue(ref int x)
{
    x = x * 2;
}

int num = 10;
ModifyValue(ref num); // 此处必须使用 ref 关键字
// 现在 num 的值为 20

2 使用 out 关键字: 使用 out 关键字声明的参数不需要在方法调用时初始化。方法必须在返回之前为 out 参数分配一个值。

点击查看代码
void GetValue(out int x)
{
    x = 20;
}

int num;
GetValue(out num); // 使用 out 关键字
// 现在 num 的值为 20

3 引用参数与返回值的关系: 在使用引用参数时,方法可以通过参数直接修改调用方提供的变量,而不需要返回值。这使得方法可以同时返回多个值,而不仅限于单个返回值的形式。

引用参数可以在需要修改调用方提供的变量值而不使用返回值的情况下非常有用。然而,需要谨慎使用,因为过多的引用参数可能会使代码难以理解和维护。

*1 使用引用参数时,必须在方法和调用中都使用ref修饰符。

点击查看代码
using System;

class Program
{
    static void ModifyValue(ref int x) // 声明方法时使用ref关键字
    {
        x = x * 2;
    }

    static void Main()
    {
        int num = 10;
        ModifyValue(ref num); // 调用方法时也使用ref关键字
        Console.WriteLine($"Modified value: {num}"); // 输出结果:Modified value: 20
    }
}

*2 实参必须是变量,在用作实参前必须要被复制。如果是引用类型变量,可以赋值一个引用或null。

点击查看代码
using System;

class Program
{
    static void UpdateString(ref string str) // 声明方法时使用ref关键字
    {
        str = "Updated string";
    }

    static void Main()
    {
        string text = "Original string";
        string reference = text; // 创建一个引用,指向 text 变量

        UpdateString(ref reference); // 调用方法时使用引用类型变量 //ref被重新指向了堆中的str

         //输出了错误的结果
        Console.WriteLine($"Updated text: {text}"); // 输出结果:Updated text: Original string
        Console.WriteLine($"Reference: {reference}"); // 输出结果:Reference: Updated string  
    }
}

Tip:
对于值参数,系统在栈上为形参分配内存。
对于引用参数:

  • 不会在栈上为形参分配内存。
  • 形参的参数名将作为实参的别名,指向相同的内存位置。
    由于形参与实参名指向相同的内存位置,所以在方法执行过程中对形参做的任何改变在方法完成后依然课件。
点击查看代码
    class Progrma
    {
        static void MyMethod(ref MyClass f1,ref int f2)
        {
            f1.Val = f1.Val+5;  //参数的字段加5
            f2 = f2 + 5;

            Console.WriteLine($"f1.Val:{f1.Val},f2:{f2}");
        }

        
        static void Main()
        {
            MyClass a1 = new MyClass();
            int a2 = 10;

            MyMethod(ref a1,ref a2);
            Console.WriteLine($"a1.Val:{a1.Val},f2:{a2}");
        }
     }

  • 在方法调用之前,将要被用作实参的变量a1和a2已经在栈里了。
  • 在方法的开始,形参名被设置为实参的别名。变量a1和f1引用相同的内存位置,a2和f2引用相同的内存位置。
  • 方法结束时,形参名失效,值类型a2和引用类型a1所指向的对象的值都被方法内的行为改变了。

引用类型作为值参数和引用参数

在方法内设置引用类型形参时会发生什么

-将引用类型对象作为[值参数]传递
如果在方法内创建一个新对象并赋值给形参,将切断形参与实参之间的关联。并且在方法调用结束后,新对象也将不复存在。

-将引用类型对象作为引用参数传递
如果在方法内创建一个新对象并赋值给形参,在方法结束后改对象依然存在,并且是实参所引用的值。

第一种情况:将引用类型对象作为【值参数】传递:

点击查看代码
    class MyClass
    {
        public int Val = 20;    //初始化字段为20
    }

    class Progrma
    {
            static void RefAsParameter(MyClass f1)
      {
       
          f1.Val = 50;
          Console.WriteLine($"After member assignment:{f1.Val}");

          f1 = new MyClass();

          Console.WriteLine($"After new object creation:{f1.Val}");

      }


      static void Main()
      {
          MyClass a1 = new MyClass();  //堆中创建新的成员,字段被

          Console.WriteLine($"Before method call:{a1.Val}");

          RefAsParameter(a1);

          Console.WriteLine($"After method call:{a1.Val}");

      }
    }

/*
Before method call:20
After member assignment:50
After new object creation:20
After method call:50
*/

  • 方法开始时,实参和形参指向堆中相同的对象。
  • 在位对象的成员赋值之后,它们仍指向堆中相同的对象。
  • 当方法分配新的对象并赋值给形参时,(方法外部的)实参仍指向原始对象,而形参指向的是新对象。
  • 在方法调用之后,实参指向原始对象,形参和新对象都会消失。

第二种情况:将引用类型对象作为【引用参数】传递:

点击查看代码

    class MyClass
    {
        public int Val = 20;    //初始化字段为20
    }

    class Progrma
    {
            static void RefAsParameter(ref MyClass f1)
      {
       
          f1.Val = 50;
          Console.WriteLine($"After member assignment:{f1.Val}");

          f1 = new MyClass();

          Console.WriteLine($"After new object creation:{f1.Val}");

      }


      static void Main()
      {
          MyClass a1 = new MyClass();  //堆中创建新的成员,字段被

          Console.WriteLine($"Before method call:{a1.Val}");

          RefAsParameter(ref a1);

          Console.WriteLine($"After method call:{a1.Val}");

      }
    }
    
/*
Before method call:20
After member assignment:50
After new object creation:20
After method call:20
*/

  • 在方法调用时,形参和实参都指向堆中相同的对象。
  • 对成员值的修改会同时影响到形参和实参。
  • 当方法创建新的对象并赋值给形参时,形参和实参的引用都指向新对象。
  • 在方法结束后,实参指向在方法内创建的新对象。(形参和原始对象都没了。)

输出参数

输出参数用于从方法体内把数据传出到调用代码,它们的行为与引用参数相似。
输出参数有以下要求:

  • 必须在声明和调用中都使用修饰符。输出参数的修饰符是out。
  • 和引用参数相似,实参必须是变量,而不能是其它类型的表达式。(因为方法必须要有内存位置来保留返回值)

与引用参数类似,输出参数的形参充当实参的别名。形参和实参都是同一块内存位置的名称。
在方法内对形参做的任何改变,在方法执行完成之后(通过实参变量)都是可见的。

与引用参数不同,输出的参数有以下要求:

  • 在方法内部,给实参赋值后才能读取它。这意味着参数的初始值是无关的,而且没有必要在方法调用之前为实参赋值。(代码中任何执行路径试图在方法给输出参数赋值之前读取它的值,编译器会报错)
  • 在方法内部,在方法返回之前,代码中每条可能的输出路径都必须为所以输出参数赋值。
点击查看代码
    class Progrma
    {
          static void MyMethod(out MyClass f1,out int f2)
      {
       
          f1 = new MyClass();
          f1.Val = 25;
          f2 = 15;
          //Console.WriteLine($"After member assignment:{f1.Val}");


          //Console.WriteLine($"After new object creation:{f1.Val}");
          Console.WriteLine($"para1 in method:{f1.Val},para12 in method:{f2}");

      }


      static void Main()
      {
          MyClass a1 = null;

    //Console.WriteLine($"Before method call:{a1.Val}");
    
    int a2;

    MyMethod(out a1,out a2);

    Console.WriteLine($"arg1:{a1.Val},arg2:{a2}");

    }
   }

/*
para1 in method:25,para12 in method:15
arg1:25,arg2:15
*/

  • 在方法调用之前,将要被用作实参的变量a1和a2就已经在栈中了。
  • 方法开始时,形参的名称被设置为实参的别名。变量a1、a2和f1、f2指向的是相同的内存位置。a1和a2不在作用域之内,所以不能在MyMethod中访问。
  • 在方法内部,代码创建了一个MyClass对象并把它赋值给了f1。然后赋一个值给f1的字段,也赋一个值给f2。对f1和f2的赋值都是必须的,因为它们是输出函数。
  • 方法执行之后,形参的名称已经失效(形参消失),但是引用类型的a1和值类型的a2的值都被方法的行为改变了。

*新的语法中,可以消除显示的变量声明
直接在方法调用时加入变量类型声明。

static void Main()
{
MyMethod(out MyClass a1,out int a2); //调用方法
}

*上例中,虽然a1和a2只在方法调用时进行了声明。方法结束后,a1和a2仍然可以使用。(实参的内存位置不会因为方法结束而消失。)

参数数组

之前的参数类型中,一个形参必须严格对应一个实参。参数数组则不同,它允许特定类型的零个或多个实参对应一个特定的形参。

  • 在一个参数列表中,只能有一个参数数组。
  • 如果有,必须是列表的最后一个。
  • 由参数数组表示的所有参数必须是同一类型。

声明一个参数数组时必须做的事如下:

  • 在数据类型前使用params修饰符
  • 在数据类型后放置一组空的方括号

void ListInts(params int[] invals) //invals是参数名称
数组是一个引用类型,所以它的所有数据项都保存在堆中。

两种调用方法:
ListInts(10,20,30); //逗号分割的列表(数组类型要对应)
ListInts{1,2,3} //一维数组(数组类型要对应)

调用时没使用params修饰符。
其它类型是一致的,要么都使用,要么不使用。
数组是声明的时候使用,调用的时候不使用。

方法调用的形式:
延伸式
【在不知道会传几个参数时使用】

点击查看代码
    class MyClass
    {
        
        public void ListInts(params int[] inVals)
        {
            if((inVals != null) && (inVals.Length !=0))
                for (int i=0;i<inVals.Length;i++)
                {
                    inVals[i] *= 10;
                    Console.WriteLine($"inVals[{i}]={inVals[i]}");
                }
        }
    }

    class Progrma
    {
        static void Main()
        {
            int first = 5,second = 6,third = 7;

            MyClass mc = new MyClass();

            mc.ListInts(first,second,third);

            Console.WriteLine($"first = {first},second = {second},third = {third}");
        }

    /*

      inVals[0]=50
      inVals[1]=60
      inVals[2]=70
      first = 5,second = 6,third = 7

    */

  • 方法调用前,3个实参已经在栈里了。
  • 方法开始时,3个实参被用于初始化堆中的数组,并且数组的引用被赋值给形参inVals。
  • 在方法内部,代码首先确认数组引用不是null,然后处理数组。
  • 方法执行后,形参失效
    关于参数数组,需要记住:当数组在堆中被创建时,实参的值被复制到数组中。(和值参数类似)
  • 如果数组参数是值类型,那么被复制,实参在方法内部不受影响。
  • 如果数组参数是引用类型,那么引用被复制,实参引用的对象在方法内部会受到影响。

将数组作为实参###

可以在方法调用之前创建并组装一个数组,将单一的数组变量作为实参传递。
这样,编译器会使用你创建的数组,而不是重新创建一个。

下例:
将数组作为实参传入,引用方法直接作用于数组。

总结:
与传入多个实参初始化数组不同,方法执行后数组因为作为实参会变化,初始化的数组在方法执行后会作为形参失效。

点击查看代码
class MyClass
{
    
    public void ListInts(params int[] inVals)
    {
        if((inVals != null) && (inVals.Length !=0))
            for (int i=0;i<inVals.Length;i++)
            {
                inVals[i] *= 10;
                Console.WriteLine($"inVals[{i}]={inVals[i]}");
            }
    }
}
class Progrma
{
    static void Main()
    {
        int[] myArr = new int[] { 3, 4, 5 };
        MyClass mc = new MyClass();

        mc.ListInts(myArr);

        foreach (int i in myArr)
            Console.WriteLine($"{i}");
    }

/*
inVals[0]=30
inVals[1]=40
inVals[2]=50
30
40
50


*/

参数类型总结

参数类型 修饰符 是否在声明时使用 是否在调用时使用 执行
系统吧实参的值复制给形参
引用 ref 形参是实参的别名
输出 out 仅包含一个返回的值,形参是实参的别名
数组 params 允许传递可变数目的实参到方法

ref局部变量和ref返回

ref返回功能,允许将一个引用发送到方法外,然后在上下文内使用这个引用。

相关功能ref的局部变量

  • 可以使用这个功能给一个变量创建别名,即使引用类型是个值类型。
  • 对任意一个变量的赋值都会反映到另一个变量上,因为它们引用的事相同的对象。

创建别名的语法:
ref int y = ref x;

点击查看代码

using System;

class Program
{
    static void Main()
    {
        int x = 10;
        int y = 20;

        Console.WriteLine($"Before: x = {x}, y = {y}");
        Swap(ref x, ref y);
        Console.WriteLine($"After: x = {x}, y = {y}");
    }

    static void Swap(ref int a, ref int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
}


别名功能并不是ref局部变量功能的最常见用途。它经常和ref返回功能一起使用。
ref返回功能提供了一种使方法返回变量引用,而不是变量值的方法。

语法:

  • 一次是在犯法的返回类型声明之前
  • 另一次是在return关键字之后,被返回对象的变量名之前。

ref的作用:
示例 1: 交换变量的值

点击查看代码
using System;

class Program
{
    static void Main()
    {
        int x = 10;
        int y = 20;

        Console.WriteLine($"Before: x = {x}, y = {y}");
        Swap(ref x, ref y);
        Console.WriteLine($"After: x = {x}, y = {y}");
    }

    static void Swap(ref int a, ref int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
}

示例 2: 使用ref修改传入参数的值

点击查看代码
using System;

class Program
{
    static void Main()
    {
        int number = 5;
        Console.WriteLine($"Before: number = {number}");
        Square(ref number);
        Console.WriteLine($"After: number = {number}");
    }

    static void Square(ref int num)
    {
        num *= num;
    }
}

示例 3: 使用out返回多个值
虽然C#不直接支持返回多个值,但可以通过使用ref参数来实现类似的效果。

点击查看代码
using System;

class Program
{
    static void Main()
    {
        int result;
        bool success = TryParse("123", out result);
        
        if (success)
            Console.WriteLine($"Parsed successfully: {result}");
        else
            Console.WriteLine("Failed to parse");
    }

    static bool TryParse(string str, out int number)
    {

        //int.TryParse 是 C# 中用于尝试将字符串转换为整数的一个方法。它返回一个布尔值
        return int.TryParse(str, out number);
    }
}

示例 4: 使用out返回数组中的最大和最小值
这个示例展示了如何使用ref关键字来获取数组中的最大和最小值。

点击查看代码
using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 3, 5, 7, 2, 8 };
        int min, max;

        GetMinMax(numbers, out min, out max);
        
        Console.WriteLine($"Min: {min}, Max: {max}");
    }

    static void GetMinMax(int[] numbers, out int min, out int max)
    {
        min = int.MaxValue;
        max = int.MinValue;

        foreach (int number in numbers)
        {
            if (number < min)
                min = number;
            if (number > max)
                max = number;
        }
    }
}

想返回的是包含较大值的变量的引用,而不是实际的值。

示例:

点击查看代码

using System;

class Program
{
    static ref int Max(ref int p1,ref int p2)
    {
        if(p1 > p2) 
            return ref p1;  //返回的引用,而不是值
        else
            return ref p2;  //返回的引用,而不是值
        
    }

    static void Main()
    {
        int v1 = 10;
        int v2 = 20;

        Console.WriteLine("Start");
        Console.WriteLine($"v1:{v1},v2:{v2}\n");

        ref int max = ref Max(ref v1,ref v2);
        Console.WriteLine("After assignment");
        Console.WriteLine($"max:{max}\n");

        max++;

        Console.WriteLine("After increment");
        Console.WriteLine($"max:{max},v1:{v1},v2:{v2}");
    }
}

说明:
ref关键字的作用
参数传递:在参数传递中,ref关键字使参数通过引用传递,而不是通过值传递。这样方法内部对参数的修改会反映到方法外部。
返回值:当一个方法使用ref返回值时,它返回的是变量的引用,而不是变量的值。这允许调用者通过引用直接操作方法内部的变量。

在这个示例中,ref关键字的作用在于:

  • Max方法返回较大值变量的引用。
  • Main方法中通过ref int max获取到这个引用,并直接修改它,进而影响到原始变量。

限制:
不能将返回类型是void的方法声明为ref返回方法。
ref return不能返回如下内容:

  • 空值

  • 常量

  • 枚举成员

  • 类或者结构体的属性

  • 指向只读位置的指针

  • ref return表达式只能指向原先就在调用域内的位置,或者字段。所以,它不能指向方法的局部变量。
    *一个 ref return 方法不能返回方法内部的局部变量,因为这些变量在方法结束后会被销毁。

点击查看代码
using System;

class Program
{
    static ref int GetRefToElement(int[] array, int index)
    {
        return ref array[index]; // 合法,因为 array 是调用域的参数
    }

    // 这个方法是非法的
    // static ref int GetLocalRef()
    // {
    //     int local = 42;
    //     return ref local; // 不合法,不能返回局部变量的引用
    // }

    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        ref int refToElement = ref GetRefToElement(numbers, 2);
        refToElement = 42; // 修改数组中的第三个元素
        Console.WriteLine(string.Join(", ", numbers)); // 输出: 1, 2, 42, 4, 5
    }
}

  • ref局部变量只能被赋值一次(一旦初始化,就不能指向其它位置了)
点击查看代码
using System;

class Program
{
    static void Main()
    {
        int a = 10;
        int b = 20;
        ref int refVar = ref a; // 初始化 ref 变量
        // refVar = ref b; // 不合法,不能重新赋值
        refVar = 30; // 合法,修改 a 的值
        Console.WriteLine($"a: {a}, b: {b}"); // 输出: a: 30, b: 20
    }
}

  • 即使将一个方法申明为ref返回方法,如果在调用该方法时省略了ref 关键字,则返回的将是值,而不是指向值的内存位置的指针。
点击查看代码
using System;

class Program
{
    static ref int GetRefToElement(int[] array, int index)
    {
        return ref array[index];
    }

    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        int value = GetRefToElement(numbers, 2); // 省略了 ref 关键字,得到的是值的副本
        value = 42; // 修改副本,不会影响原数组
        Console.WriteLine(string.Join(", ", numbers)); // 输出: 1, 2, 3, 4, 5
    }
}


  • 如果将ref局部变量作为常规的实际参数传递给其他方法,则该方法仅获取该变量的一个副本。尽管ref局部变量包含指向存储位置的指针,但当以这种方式使用时,它会传递值而不是引用。
点击查看代码
using System;

class Program
{
    static void ModifyValue(int value)
    {
        value = 100; // 只修改了副本
    }

    static void Main()
    {
        int a = 10;
        ref int refVar = ref a;
        ModifyValue(refVar); // 传递的是 refVar 的副本
        Console.WriteLine($"a: {a}"); // 输出: a: 10
    }
}


点击查看代码
using System;

class Program
{
    static void ModifyValue(ref int value)
    {
        value = 100; // 只修改了副本
    }

    static void Main()
    {
        int a = 10;
        ref int refVar = ref a;
        ModifyValue(ref refVar); // 传递的是 refVar 的副本
        Console.WriteLine($"a: {a}"); // 输出: a: 100
    }
}

局部变量 是指在方法内声明和使用的变量,它们的生命周期仅限于方法执行期间。

posted @ 2024-05-30 12:07  NiuFacai  阅读(91)  评论(0)    收藏  举报