C#学习 - 参数、拓展方法

传值参数

在声明时不带修饰符的形参就是值参数
一个值参数对应一个局部变量,它的初始值来自该方法调用所提供的相应实参。值参数是实参的副本,对值参数的操作不会影响变量的值

  • 传值参数 - 值类型
internal class Program
{
    static void Main(string[] args)
    {
        Tool tool = new Tool();
        int x = 20;
        int y = 40;
        tool.Add(x, y);
        Console.WriteLine(x);
        //打印60和20
    }
}
class Tool
{
    public void Add(int x, int y)
    {
        x = x + y;
        Console.WriteLine(x);
    }
}
  • 传值参数 - 引用类型,创建新对象
    引用类型的变量存储的值是引用类型实例在堆内存中的地址
    引用类型的变量引用一个实例后传入方法会产生一个副本,此副本存储着一个地址
internal class Program
{
    static void Main(string[] args)
    {
        User user = new User() { ID = "111" };
        Method(user);
        Console.WriteLine(user.ID);
        //打印123和111
    }
    static void Method(User user)
    {
        user = new User() { ID = "123" };
        Console.WriteLine(user.ID);
    }
}
class User
{
    public string ID { get; set; }
}
  • 传值参数 - 引用类型,只操作对象,不创建新对象
    通过参数修改值后,其原本的变量所对应的值也会更改
internal class Program
{
    static void Main(string[] args)
    {
        User user = new User() { ID = "111" };
        Method(user);
        Console.WriteLine("HashCode = {0}, ID = {1}", user.GetHashCode(), user.ID);
        //此时Hash码和ID都相同
    }
    static void Method(User user)
    {
        user.ID = "123";//没有创建新对象
        //此为方法的副作用(side-effect),在实际使用中应该避免方法的这些副作用
        Console.WriteLine("HashCode = {0}, ID = {1}",user.GetHashCode(),user.ID);
    }
}
class User
{
    public string ID { get; set; }
}

引用参数

在声明时使用了 ref 修饰符声明的形参,不会创建副本,引用参数所表示的存储位置是在方法调用中作为实参给出的那个变量所表示的存储位置
变量在作为引用参数传递前,必须先明确赋值

  • 引用参数 - 值类型
    在方法内部为参数赋值后,参数获得新值,方法外的变量也获得同样的新值
static void Main(string[] args)
{
    int x = 10;
    Method(ref x);
    Console.WriteLine(x);//打印20
}
static void Method(ref int x)
{
    x += 10;
}
  • 引用参数 - 引用类型,创建新对象
    引用类型的变量传进方法后,引用参数和变量指向同一个地址,地址中存储的是另一个地址——变量所指向的对象在堆内存中的地址
    在方法体中为参数赋了新值后,变量会指向一个新创建的对象
internal class Program
{
    static void Main(string[] args)
    {
        User user = new User() { ID = "111" };
        Console.WriteLine("HashCode = {0}, ID = {1}", user.GetHashCode(), user.ID);
        Method(ref user);
        Console.WriteLine("HashCode = {0}, ID = {1}", user.GetHashCode(), user.ID);
        //打印三行
        //第一次打印与后面不同
        //方法调用后的两次的Hash码和ID都相同
    }
    static void Method(ref User user)
    {	//Hash码变化后是因为方法体中new了一个新值
        user = new User() { ID = "123" };
        Console.WriteLine("HashCode = {0}, ID = {1}", user.GetHashCode(), user.ID);
    }
}
class User
{
    public string ID { get; set; }
}
  • 引用参数 - 引用类型,不创建新对象,只改变对象值
    此时与传值参数在效果上一样,但机理不同
    Hash码不改变,只有对象的值会改变
    传值参数中:参数和变量所指向的内存地址不同
    引用参数中:参数和变量所指向的内存地址相同
internal class Program
{
    static void Main(string[] args)
    {
        User user = new User() { ID = "111" };
        Console.WriteLine("HashCode = {0}, ID = {1}", user.GetHashCode(), user.ID);
        Method(ref user);
        Console.WriteLine("HashCode = {0}, ID = {1}", user.GetHashCode(), user.ID);
        //三次打印出来后Hash码都相同
        //方法调用后,对象(ID)值改变
    }
    static void Method(ref User user)
    {
        user.ID = "123";
        Console.WriteLine("HashCode = {0}, ID = {1}", user.GetHashCode(), user.ID);
    }
}
class User
{
    public string ID { get; set; }
}

输出参数

用 out 修饰符声明的参数,输出参数不创建新的存储位置
变量在作为输出参数传递前,不需要赋值
在方法返回时,该方法的每一个输出参数都必须明确赋值
传值参数:利用副作用修改值
输出参数:利用副作用向外输出

  • 输出参数 - 值类型
    参数和变量指向的地址是同一个
    方法体中为参数赋值后,变量也会获得相同的新值
    输出参数并不需要创建变量的副本
Console.WriteLine("Input Num1:>");
string arg1 = Console.ReadLine();
double x = 0;
bool b1 = double.TryParse(arg1, out x);
//TryParse方法返回一个bool类型的值
//将string类型的值转换为double类型的值并输出
if(b1 == false)
{
    Console.WriteLine("Input error");
    return;
}
Console.WriteLine("Input Num2:>");
string arg2 = Console.ReadLine();
double y = 0;
bool b2 = double.TryParse(arg2, out y);
if (b2 == false)
{
    Console.WriteLine("Input error");
    return;
}
Console.WriteLine("{0} + {1} = {2}", x, y, x + y);
  • 输出参数 - 引用类型
    引用类型以输出参数的形式传入方法,在方法体中的参数赋值后,变量会指向一个新地址,指向的是新创建的对象在堆内存上的地址
internal class Program
{
    static void Main(string[] args)
    {
        User user = null;
        bool b = UserFactory.Create("JACK", 1233431, out user);
        if(b==true)
        {
            Console.WriteLine("User:> {0}\nCode:> {1}", user.ID, user.Code);
        }
    }
}
class User
{
    public string ID { get; set; }
    public int Code { get; set; }
}
class UserFactory//创建User
{
    public static bool Create(string userID, int userCode, out User result)
    {
        result = null;
        if (string.IsNullOrEmpty(userID))
        {
            return false;
        }
        result = new User() { ID = userID, Code = userCode };
        return true;
    }
}

数组参数

声明时由params修饰
未使用数组参数:

static void Main(string[] args)
{
    int[] intArray = { 1, 2, 3 };
    int result = CalculateSum(intArray);
    Console.WriteLine(result);
}
static int CalculateSum(int[] nums)
{
    int sum = 0;
    foreach (var i in nums) 
    {
        sum += i;
    }
    return sum;
}

使用数组参数:

static void Main(string[] args)
{
    int result = CalculateSum(1, 2, 3);//无须声明一个数组
    Console.WriteLine(result);
}
static int CalculateSum(params int[] nums)
{						//数组参数
    int sum = 0;
    foreach (var i in nums) 
    {
        sum += i;
    }
    return sum;
}

数组参数一些例子:

  • Console.WriteLine
int x = 100;
int y = 200;
int z = x + y;
Console.WriteLine("{0} + {1} = {2}", x, y, z);
  • Split 方法
string str = " USSR; MOS, RS";
string[] result = str.Split(';', ',');//Split可以分割字符串
foreach (string area in result)
{
    Console.WriteLine(area);
}

注意:在一个方法的参数列表中,只能有一个参数由params修饰,且此参数为参数列表中最后一个参数

具名参数

调用方法时,传入的参数带有名字
不具名调用:

static void Main(string[] args)
{
    Print("Jack", 18);
}
static void Print(string name, int age)
{
    Console.WriteLine("{0} is {1} years ago", name, age);
}

具名调用:

static void Main(string[] args)
{
    Print(name: "Jack", age: 18);
}
static void Print(string name, int age)
{
    Console.WriteLine("{0} is {1} years ago", name, age);
}

优点:

  • 可以提高代码可读性
  • 传入参数的顺序不再受到参数列表的限制

可选参数

在调用某个方法时,此参数可传入也可以不传入
当不写时,此参数默认获得声明时的值

static void Main(string[] args)
{
    Print(name: "John");
}
static void Print(string name = "Jack", int age = 18)
{
    Console.WriteLine("{0} is {1} years ago", name, age);
}

扩展方法(this参数)

double pi = 3.1415926;
double a = Math.Round(pi, 2);
//Round方法第一个值是对谁四舍五入
//第二个值是精确到第几位
Console.WriteLine(a);

double类型没有Round方法,只能依靠Math
而扩展方法可以使用double类型调用Round方法

internal class Program
{
    static void Main(string[] args)
    {
        double pi = 3.1415926;
        double a = pi.Round(2);//.前面的pi就是第一个参数,只输入第二个参数
        Console.WriteLine(a);
    }
}
static class DoubleExtension
{
    public static double Round(this double input, int digits)
    {
        double result = Math.Round(input, digits);
        return result;
    }
}

注意:

  • 方法必须是公有的、静态的(被public static修饰)
  • 必须是参数列表中的第一个,由this修饰
  • 必须由一个静态类(一般类名为SomeTypeExtension)来统一收纳SomeType类型的扩展方法

LINQ方法

写一个能接收集合类型的参数的方法,判断集合中的值是否都大于10
未使用LINQ方法:

static void Main(string[] args)
{
    List<int> myList = new List<int>() { 10, 20, 30 };
    bool b = ItemBiggerTen(myList);
    Console.WriteLine(b);
}
//上为方法调用,下为方法
static bool ItemBiggerTen(List<int> intList)
{
    foreach (int i in intList)
    {
        if (i <= 10)
        {
            return false;
        }
    }
    return true;
}

使用LINQ方法:

static void Main(string[] args)
{
    List<int> myList = new List<int>() { 10, 20, 30 };
    bool b = myList.All(i => i > 10);//查看所有元素是否都大于10
    //All方法可以接收委托类型的参数
    Console.WriteLine(b);
}

上段代码中的 All() 方法就是扩展方法,不属于List类,而属于Enumerable这个静态类

各种参数使用场景

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

你可能感兴趣的:(C#学习,c#,学习,开发语言)