018 传值、输出、引用、数组、具名、可选参数、扩展方法

简介

《C# 语言入门详解》是刘铁锰老师录制的高质量中文 C# 视频教程。

视频链接:B站Youtube

本文内容包含视频教程的018节的主要内容,转自语雀平台月江流 ,针对此内容将建一个专栏将东西比较多,知识比较重要的章节转载到自己的个人博客,学而时习之。

这一节主要讲参数,参数是方法的一部分,所以这节课也可以看作是对方法的进一步学习。

本节内容:

  • 传值参数
  • 输出参数
  • 引用参数
  • 数组参数
  • 具名参数
  • 可选参数
  • 扩展方法(this 参数)

值参数

image

参见 C# 图解教程 第五章 方法。

传值参数 -> 值类型

image

传值参数 -> 引用类型,并且新创建对象

image

这种状况很少见,一般情况都是传进来引用它的值,而不是连接到新对象去(基本只有面试题会考这个)。

注:当参数类型为 string 时,在方法内部修改参数的值,对应的是此处创建对象这种情况。

因为 string 是 immutable 的,所以在方法内部对 string 赋值实际是“创建新的 string 实例再赋值”,最终方法外部的 string 并不会改变。

GetHashCode()

Object.GetHashCode() 方法,用于获取当前对象的哈希代码,每个对象的 Hash Code 都不一样。

通过 Hash Code 来区分两个 Name 相同的 stu 对象。

class Program
{
    static void Main(string[] args)
    {
        var stu = new Student() { Name="Tim"};
        SomeMethod(stu);
        Console.WriteLine(stu.Name);

        Console.WriteLine(stu.GetHashCode());
    }

    static void SomeMethod(Student stu)
    {
        stu = new Student { Name = "Tim" };
        Console.WriteLine(stu.Name);
        Console.WriteLine(stu.GetHashCode());
    }
}

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

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

image

这种通过传递进来的参数修改其引用对象的值的情况,在工作中也比较少见。

因为作为方法,其主要输出还是靠返回值。我们把这种修改参数所引用对象的值的操作叫做方法的副作用(side-effect),这种副作用平时编程时要尽量避免。

引用参数 ref

image

引用参数 -> 值类型

image
static void Main(string[] args)
{
    int y = 1;
    IWantSideEffect(ref y);
    Console.WriteLine(y);
}

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

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

image
class Program
{
    static void Main(string[] args)
    {
        var outterStu = new Student() { Name = "Tim" };
        Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);
        Console.WriteLine("-----------------");
        IWantSideEffect(ref outterStu);
        Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);
    }

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

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

引用参数 -> 引用类型,不创建新对象只改变对象值

image

对象的 HashCode 没有改变过。

class Program
{
    static void Main(string[] args)
    {
        var outterStu = new Student() { Name = "Tim" };
        Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);
        Console.WriteLine("-----------------");
        SomeSideEffect(ref outterStu);
        Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);
    }

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

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

注:上面示例中使用传值参数(不用 ref)结果也将一样,但内部机理不同。
传值参数创建了副本,方法里面的 stu 和 outterStu 不是一个对象,所指向的内存地址不一样,但是存储的地址是相同的,都存储的是 Student 实例在堆内存中的地址。

引用参数 stu 和 outterStu 指向的是同一个内存地址,这个内存地址里面存储的就是 Student 实例在堆内存中的地址。

输出形参 out

image

输出参数 -> 值类型

image
static void Main(string[] args)
{
    Console.WriteLine("Please input first number:");
    var arg1 = Console.ReadLine();
    double x = 0;
    if (double.TryParse(arg1, out x) == false)
    {
        Console.WriteLine("Input error!");
        return;
    }

    Console.WriteLine("Please input second number:");
    var arg2 = Console.ReadLine();
    double y = 0;
    if (double.TryParse(arg2, out y) == false)
    {
        Console.WriteLine("Input error!");
        return;
    }

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

自己实现了带有输出参数的 TryParse:

class Program
{
    static void Main(string[] args)
    {
        double x = 0;
        if(DoubleParser.TryParse("aa",out x))
        {
            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

引用类型的输出参数实例

class Program
{
    static void Main(string[] args)
    {
        Student stu = null;
        if(StudentFactory.Create("Tim", 34, out stu))
        {
            Console.WriteLine("Student {0}, age is {1}",stu.Name,stu.Age);
        }
    }
}

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))
        {
            return false;
        }

        if (stuAge < 20 || stuAge > 80)
        {
            return false;
        }

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

数组参数 params

  • 必需是形参列表中的最后一个,由 params 修饰
  • 举列:String.Format 方法和 String.Split 方法

使用 params 关键字前:

class Program
{
    static void Main(string[] args)
    {
        var myIntArray = new int[] { 1, 2, 3 };
        int result = CalculateSum(myIntArray);
        Console.WriteLine(result);
    }

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

        return sum;
    }
}

使用 params 后,不再需要单独声明数组:

class Program
{
    static void Main(string[] args)
    {
        int result = CalculateSum(1, 2, 3);
        Console.WriteLine(result);
    }

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

        return sum;
    }
}

我们早在 WriteLine 方法中就用到了 params。

image

又一个用到了数组参数(params)的例子。

image

具名参数

具名参数:参数的位置不再受约束。

具名参数的优点:

  • 提高代码可读性
  • 参数的位置不在受参数列表约束

具名参数实例:

class Program
{
    static void Main(string[] args)
    {
        PrintInfo("Tim", 34);

        PrintInfo(age: 24, name:"Wonder");
    }

    static void PrintInfo(string name, int age)
    {
        Console.WriteLine("Helllo {0}, you are {1}.",name,age);
    }
}

可选参数

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

扩展方法

image

无扩展方法:

class Program
{
    static void Main(string[] args)
    {
        double x = 3.14159;
        // double 类型本身没有 Round 方法,只能使用 Math.Round。
        double y = Math.Round(x, 4);
        Console.WriteLine(y);
    }
}

有扩展方法后:

class Program
{
    static void Main(string[] args)
    {
        double x = 3.14159;
        // double 类型本身没有 Round 方法,只能使用 Math.Round。
        double y = x.Round(4);
        Console.WriteLine(y);
    }
}

static class DoubleExtension
{
    public static double Round(this double input,int digits)
    {
        return Math.Round(input, digits);
    }
}
image

当我们无法修改类型源码时,可以通过扩展方法为目标数据类型追加方法。
LINQ 也是扩展方法的一大体现。

LINQ 实例

class Program
{
    static void Main(string[] args)
    {
        var myList = new List<int>(){ 11, 12, 9, 14, 15 };
        //bool result = AllGreaterThanTen(myList);
        // 这里的 All 就是一个扩展方法
        bool result = myList.All(i => i > 10);
        Console.WriteLine(result);
    }

    static bool AllGreaterThanTen(List<int> intList)
    {
        foreach (var item in intList)
        {
            if (item <= 10)
            {
                return false;
            }
        }

        return true;
    }
}

All 第一个参数带 this,确实是扩展方法。

image

总结

各种参数的使用场景总结:

  • 传值参数:参数的默认传递方法
  • 输出参数:用于除返回值外还需要输出的场景
  • 引用参数:用于需要修改实际参数值的场景
  • 数组参数:用于简化方法的调用
  • 具名参数:提高可读性
  • 可选参数:参数拥有默认值
  • 扩展方法(this 参数):为目标数据类型“追加”方法
© 版权声明
THE END
喜欢就支持一下吧
点赞25赞赏
分享
评论 抢沙发