传值参数

  • 声明时不带修饰符的形参是值形参。一个值形参对应于一个局部变量,只是它的初始值来自该方法调用所提供的相应实参
  • 当形参是值形参时,方法调用中的对应实参必须是表达式,并且它的类型可以隐式转换为形参的类型
  • 允许方法将新值赋给值参数。这样的赋值只影响由该值形参表示的局部存储位置,而不会影响在方法调用时由调用方给出的实参

传值参数 —>值类型

  • 图解如下:

image.png

  • 示例: ```csharp using System;

namespace ParamentersExample { class Program { static void Main(string[] args) { Student stu = new Student(); int y = 100; stu.AddOne(y);//输出101 Console.WriteLine(y);//输出100 } }

  1. class Student
  2. {
  3. //x是一个值参数,数据类型是int值类型
  4. public void AddOne(int x)
  5. {
  6. x++;
  7. Console.WriteLine(x);
  8. }
  9. }

}


<a name="xhrWr"></a>
## 传值参数 —>引用类型,并且新创建对象

   - 图解如下:(实际应用并不多)

![image.png](https://cdn.nlark.com/yuque/0/2021/png/21507654/1623375099937-407024bc-6850-4c4e-917c-0dddbb5da3d2.png#clientId=ud0073cac-bf3a-4&from=paste&height=273&id=ufe6d68b5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=545&originWidth=970&originalType=binary&ratio=2&size=331939&status=done&style=none&taskId=u487783c0-d04e-467f-8b1a-3b6b4ea8a01&width=485)

   - 示例
```csharp
using System;

namespace ParamentersExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student() { Name = "Tim" };
            SomeMethod(stu);//输出:46104728,Tom
            Console.WriteLine($"{stu.GetHashCode()},{ stu.Name}");//输出:12289376,Tim
        }

        static void SomeMethod(Student stu)
        {
            stu = new Student() { Name = "Tom" };//像这种新创建对象,平时工作中很少用,没什么意义
            Console.WriteLine($"{stu.GetHashCode()},{ stu.Name}");
        }
    }

    class Student
    {
        public string Name { get; set; }
    }
}

传值参数 —>引用类型,只操作对象,不创建新对象

  • 图解如下:

image.png

  • 示例: ```csharp using System;

namespace ParamentersExample { class Program { static void Main(string[] args) { Student stu = new Student() { Name = “Tim” }; Console.WriteLine($”{stu.GetHashCode()},{ stu.Name}”);//输出:46104728,Tim UpdateObject(stu);//输出:46104728,Tom Console.WriteLine($”{stu.GetHashCode()},{ stu.Name}”);//输出:46104728,Tom }

    static void UpdateObject(Student stu)
    {
        stu.Name = "Tom" ;
        //方法的副作用,side-effect。只更新对象,而不创建新对象。在编程时尽量避免
        //在现实工作中,通过传进来的参数修改引用对象的值的现象较少见,因为作为方法而言,它的主要输出还是依靠返回值来实现
        Console.WriteLine($"{stu.GetHashCode()},{ stu.Name}");
    }
}

class Student
{
    public string Name { get; set; }
}

}


<a name="OZHrr"></a>
# 引用参数

- 引用形参是用ref修饰符声明的形参。与值形参不同,引用形参并不创建新的存储位置。相反,引用形参表示的存储位置恰是在方法调用中作为实参给出的那个变量所表示的存储位置
- 当形参为引用形参时,方法调用中的对应实参必须由关键字ref并后接一个与形参类型相同的variable-reference组成。变量在可以作为引用形参传递之前必须先明确赋值

<a name="DWEnt"></a>
## 引用参数 —>值类型

   - 图解如下:

![image.png](https://cdn.nlark.com/yuque/0/2021/png/21507654/1623463420513-19a32006-d7e4-4612-b55f-5979b40f7ccb.png#clientId=u4e883920-5980-4&from=paste&height=276&id=u78bfb055&margin=%5Bobject%20Object%5D&name=image.png&originHeight=551&originWidth=913&originalType=binary&ratio=2&size=341406&status=done&style=none&taskId=u7ad13b5b-e5b2-4949-a4a5-dc19b53763a&width=456.5)

   - 示例:
```csharp
class Program
{
    static void Main(string[] args)
    {
        int y = 1;
        IWantSideEffect(ref y);
        Console.WriteLine(y);//输出:101
    }

    static void IWantSideEffect(ref int x)
    {
        x = x + 100;
    }
}

引用参数 —>引用类型,创建新对象

  • 图解如下:

image.png

  • 示例: ```csharp using System;

namespace ParamentersExample { class Program { static void Main(string[] args) { Student outterStu = new Student() { Name = “Tim” }; Console.WriteLine($”{outterStu.GetHashCode()},{outterStu.Name}”);//输出:46104728,Tim Console.WriteLine(“——————————————————————-“); IWantSideEffect(ref outterStu);//输出:12289376,Tom Console.WriteLine($”{outterStu.GetHashCode()},{outterStu.Name}”);//输出:12289376,Tom }

    static void IWantSideEffect(ref Student stu)
    {
        stu = new Student() { Name = "Tom" };
        Console.WriteLine($"{stu.GetHashCode()},{stu.Name}");
    }
}

class Student
{
    public string Name { get; set; }
}

}


<a name="x0eaB"></a>
## 引用参数 —>引用类型,不创建新对象只改变对象值

   - 图解如下:(实际应用并不多)

![image.png](https://cdn.nlark.com/yuque/0/2021/png/21507654/1623465349110-3ac2021a-63d3-4b55-a475-3e118bd4a93c.png#clientId=u4e883920-5980-4&from=paste&height=250&id=udef8bbbb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=500&originWidth=1014&originalType=binary&ratio=2&size=349513&status=done&style=none&taskId=ua77c4c62-b420-4137-8890-1cb4ac9e05d&width=507)

   - 示例:注意与传值参数的引用类型,不创建对象的示例结果虽然相同,但是两者的内存机理不一样
      - 传值参数在内存中创建了实际参数的副本,即stu内部参数和outterStu外部变量,两个副本指向的内存地址不是同一内存地址,但两个不一样的内存地址存储的是同一个实例在堆内存中的地址
      - 引用参数在内存中没有创建实际参数的副本,即stu内部参数和outterStu外部变量,两个副本指向的内存地址是同一内存地址,并且这个内存地址存储的是同一个实例在堆内存中的地址
```csharp
using System;

namespace ParamentersExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student outterStu = new Student() { Name = "Tim" };
            Console.WriteLine($"{outterStu.GetHashCode()},{outterStu.Name}");//输出:46104728,Tim
            Console.WriteLine("---------------------------------------------");
            SomeSideEffect(ref outterStu);//输出:46104728,Tom
            Console.WriteLine($"{outterStu.GetHashCode()},{outterStu.Name}");//输出:46104728,Tom
        }

        static void SomeSideEffect(ref Student stu)
        {
            stu.Name = "Tom";
            Console.WriteLine($"{stu.GetHashCode()},{stu.Name}");
        }
    }

    class Student
    {
        public string Name { get; set; }
    }
}

输出参数

  • 用out修饰符声明的形参是输出形参。类似于引用形参,输出形参不创建新的存储位置。相反,输出形参表示的存储位置恰是在该方法调用中作为实参给出的那个变量所表示的存储位置
  • 当形参为输出参数时,方法调用中的相应实参必须由关键字out并后接一个与形参类型相同的variable-reference组成。变量在可以作为输出形参传递之前不一定需要明确赋值,但是在将变量作为输出形参传递的调用之后,该变量被认为是明确赋值的
  • 在方法内部,与局部变量相同,输出形参最初被认为是未赋值的,因而必须要使用它的值之前明确赋值
  • 在方法返回之前,该方法的每个输出形参都必须明确赋值
  • 声明为分部方法或迭代器的方法不能有输出形参

输出参数 —>值类型

  • 图解如下:

image.png

  • 示例1:调用C#自带的带有输出参数的TryParse方法 ```csharp static void Main(string[] args) { Console.WriteLine(“Please input first number:”); string str1 = Console.ReadLine(); double x = 0; bool b1 = double.TryParse(str1, out x); if (!b1) { Console.WriteLine(“Input error!”); return; }

    Console.WriteLine(“Please input second number:”); string str2 = Console.ReadLine(); double y = 0; bool b2 = double.TryParse(str2, out y); if (!b2) { Console.WriteLine(“Input error!”); return; }

    double z = x + y; Console.WriteLine($”{x}+{y}={z}”);

}


   - 示例2:声明自定义带有输出参数的方法
```csharp
using System;

namespace ParamentersExample
{
    class Program
    {
        static void Main(string[] args)
        {
            double x = 0;
            bool b = DoubleParser.TryParse("111",out x);
            if (b)
            {
                Console.WriteLine(x);
            }
            else
            {
                Console.WriteLine(x);
            }

        }
    }

    class DoubleParser
    {
        public static bool TryParse(string input, out double result)
        {
            try
            {
                result = double.Parse(input);
                return true;
            }
            catch 
            {
                result = 0;
                return false;
            }
        }
    }
}

输出参数 —>引用类型

  • 图解如下:

image.png

  • 示例: ```csharp using System;

namespace ParamentersExample { class Program { static void Main(string[] args) { Student stu = null; bool b = StudentFactory.Create(“Tim”,34 ,out stu); if (b) { Console.WriteLine($”{stu.Age},{stu.Name}”); }
} }

class Student
{
    public int Age { get; set; }
    public string Name { get; set; }
}

class StudentFactory
{
    public static bool Create(string stuName, int stuAge, out Student result)
    {
        result = null;
        if (string.IsNullOrEmpty(stuName))
        {
            Console.WriteLine("学生名字为空或者null");
            return false;
        }
        if (stuAge<20 || stuAge>80)
        {
            Console.WriteLine("学生年龄<20 或 >80");
            return false;
        }

        result = new Student() { Name = stuName, Age = stuAge };
        return true;
    }
}

}


<a name="Vypif"></a>
# 数组参数

- 只能有一个,且必须是形参列表中的最后一个,由params修饰
- 举例:String.Format方法和String.Split方法
```csharp
using System;

namespace ParamentersExample
{
    class Program
    {
        static void Main(string[] args)
        {
            //方法调用方式1,和没有params修饰的方法调用方式一样
            int[] myIntArr = new int[] { 1, 2, 3 };
            int x = CalculateSum(myIntArr);
            Console.WriteLine(x);//输出6

            //方法调用方式2,有params修饰的方法调用,更为简洁,无需提前声明一个数组变量
            int y = CalculateSum(1, 2, 3);
            Console.WriteLine(y);//输出6

            //String.Format方法:C#中已经定义的params修饰的Console.WriteLine方法
            Console.WriteLine("{0},{1}", x, y);//输出6,6

            //String.Split方法
            string str = "Tim,Tom.Amy;Lisa";
            string[] result = str.Split(',', '.', ';');
            foreach (var name in result)
            {
                Console.WriteLine(name);//换行输出Tim  Tom  Amy Lisa
            }
        }

        static int CalculateSum(params int[] intArr)
        {
            int sum = 0;
            foreach (var item in intArr)
            {
                sum += item;
            }
            return sum;
        }
    }
}

具名参数

  • 优点
    • 提高代码的可读性
    • 参数的位置不再受约束
  • 示例

    class Program
    {
      static void Main(string[] args)
      {
          //不具名方法调用方式
          PrintInfo("Tim", 34);
    
          //具名方法调用方式
          PrintInfo(age: 20, name: "Tom");
      }
    
      static void PrintInfo(string name, int age)
      {
          Console.WriteLine($"{name},{age}");
      }
    }
    

可选参数

  • 参数因为具有默认值而变得”可选”
  • 不推荐使用可选参数
  • 示例:

    class Program
    {
      static void Main(string[] args)
      {
          PrintInfo();
      }
    
      static void PrintInfo(string name = "Tim", int age = 34)
      {
          Console.WriteLine($"{name},{age}");
      }
    }
    

扩展方法(this参数)

  • 方法必须是公有,静态的,即被public static所修饰
  • 必须是形参列表中的第一个,由this修饰
  • 必须由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeType类型的扩展方法
  • 用于无法对源码进行修改,对目标数据类型进行追加方法
  • 举例:LINQ方法 ```csharp using System; using System.Collections.Generic; using System.Linq;

namespace ParamentersExample { class Program { static void Main(string[] args) { double x = 3.14159; double y = Math.Round(x, 4); Console.WriteLine(y);

        double z = x.Round(4);
        Console.WriteLine(z);

        List<int> myList = new List<int>() { 1, 2, 3 };
        //All方法是一个扩展方法
        bool b =  myList.All(i => i > 10);
        Console.WriteLine(b);
    }
}

static class DoubleExtension
{
    //扩展方法:
    public static double Round(this double input, int digits)
    {
        double result = Math.Round(input, digits);
        return result;
    }
}

} ```

各种参数的使用场景总结

  • 传值参数:参数的默认传递方式
  • 输出参数:用于除返回值外还需要输出的场景
  • 引用参数:用于需要修改实际参数值的场景
  • 数组参数:用于简化方法的调用
  • 具名参数:提高可读性
  • 可选参数:参数拥有默认值
  • 扩展方法(this参数):为目标数据类型”追加”方法