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

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

本节内容:

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

1 值参数

定义参照C#图解教程的第五章

传值参数→值类型

示例如下:

    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student();
            int y = 100;
            stu.AddOne(y);
            Console.WriteLine(y);
            //此处打印的y值为100.这个y的值并没有改变,原因在于x为传值参数,传进来的值会在方法体中有一个副本,
            //改变的值实际为这个副本的值,它不会影响方法体外面的参数。无论怎么操作这个副本,原来的方法体外面的值都不会受影响
        }
    }

    class Student
    {
        public void AddOne(int x)//x是值参数(声明时候不带任何修饰符的参数即为值参数)
        {
            x = x + 1;
            Console.WriteLine(x);
        }
    }

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

引用类型的变量存储的是堆的地址——下图中方法内部的参数和方法外部的对象存储的都是一个地址

示例程序如下:

    class Program
    {
        static void Main(string[] args)
        {
            Student Oldstu = new Student() { Name = "Tim" };
            SomeMethod(stu);
            Console.WriteLine(stu.Name);//方法外部变量所引用的实例并没有改变
        }

        static void SomeMethod(Student stu)//方法外部的stu与方法内部的stu不冲突
        {
            stu = new Student() { Name = "Tom" };
            Console.WriteLine(stu.Name);
        }
    }

    class Student
    {
        public string Name{get; set;}//声明了一个属性(向外暴露数据时,应该使用属性,属性比字段更安全)
    }

当改了变量名时,可使用Ctrl键+句号(.)键将相关的变量名都修改掉。

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

  • 当参数类型为string时,在方法内部修改参数的值,对应的是此处创建对象这种情况。因为string是immutable的,所以在方法内部对string赋值实际是"创建新的strin实例再赋值",最终外部的string值不会发生改变

GetHashCode

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

通过Hash Code来区分两个Name相同的stu对象,示例代码如下:

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

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

    class Student
    {
        public string Name{get; set;}//声明了一个属性(向外暴露数据时,应该使用属性,属性比字段更安全)
    }

 

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

示例代码如下

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

        static void UpdataObject(Student stu)//方法外部的stu与方法内部的stu不冲突
        {
            stu.Name = "Tom";
            Console.WriteLine("HashCode = {0},Name = {1}",stu.GetHashCode(),stu.Name);
        }
    }

    class Student
    {
        public string Name{get; set;}//声明了一个属性(向外暴露数据时,应该使用属性,属性比字段更安全)
    }

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

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

2 引用参数 ref

引用参数→值类型

    class Program
    {
        static void Main(string[] args)
        {
            int y = 1;
            IWantSideEffect(ref y);
            Console.WriteLine(y);//赋值后方法外部的变量也会变成新值
        }
        static void  IWantSideEffect(ref int x)//引用参数
        {
            x = x + 100;
        }
    }

运行结果如下:

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

    class Program
    {
        static void Main(string[] args)
        {
            Student 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; }
    }

输出结果如下:

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

    class Program
    {
        static void Main(string[] args)
        {
            Student 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; }
    }

程序运行结果如下:

注意:上面的示例程序中使用的传值参数(不用ref),打印的结果也是一样的,但两者内存的机理不相同

  • 传值参数它在内存当中创建了实际参数的副本,方法里面的stu 参数与outterstu这个变量它们两个指向的内存地址是不一样的,但是这两个不一样的地址里面存储着的地址是相同的,都存储的是Student实例在堆内存中的地址。
  • 引用参数中stu参数与outterstu这个变量它们所指向的内存地址就是同一个内存地址,而在这个内存地址里面存储的就是Student实例在堆内存中的地址

3 输出参数out

输出参数→值类型

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

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

            double z = x + y;
            Console.WriteLine("{0}+{1}={2}",x,y,z) ;
        }
    }

打印的结果如下:

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

    class Program
    {
        static void Main(string[] args)
        {
            double x = 0;
            bool b = DoubleParser.TryParse("789", out x);//789更换成ABC就会解析异常
            if (b == true)
            {
                Console.WriteLine(x+1);
            }
            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;
            }
        }
    }

输出参数→引用类型

程序运行结果为:

    class Program
    {
        static void Main(string[] args)
        {
            Student stu = null;
            bool b = StudentFactory.Create("Tim", 34, out stu);
            if (b ==true)
            {
                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;
        }
    }

输出结果为:

4 数组参数

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

不使用Params关键字

    class Program
    {
        static void Main(string[] args)
        {
            int[] 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)//加上Params关键字
        {
            int sum = 0;
            foreach (var item in intArray)
            {
                sum += item;
            }

            return sum;
        }
    }

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

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

5 具名参数

  • 参数的位置不再受拘束
    class Program
    {
        static void Main(string[] args)
        {
            PrintInfo("Tim", 34);//不具名调用
            PrintInfo(name: "Tim", age: 34);//具名调用
        }
        static void PrintInfo(string name,int age)
        {
            Console.WriteLine("Hello {0},you are {1}.",name,age);
        }
    }

具名参数的优点:

  • 它可以提高代码的可读性。——具名参数可以很方便获得参数的设置,非具名调用就只能猜或者跳转到方法定义处查看代码名字
  • 具名参数的参数位置不在受参数列表顺序的约束。

严格意义上讲,具名参数并不是参数的某个种类,而是参数的使用方法

6 可选参数

  • 参数因为具有默认值而变得"可选"
  • 不推荐使用可选参数
    class Program
    {
        static void Main(string[] args)
        {
            PrintInfo();
        }
        static void PrintInfo(string name = "Tim",int age = 34)
        {
            Console.WriteLine("Hello {0},you are {1}.",name,age);
        }
    }

程序运行结果为:name和age都是默认值

7 扩展方法

无扩展方法

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

有扩展方法

 

   class Program
    {
        static void Main(string[] args)
        {
            double x = 3.14159;
            double y = x.Round(4);//扩展方法
            Console.WriteLine(y);
        }
    }

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

当我们无法修改类型源码时,可以通过扩展方法为目标数据类型追加方法

LINQ(语言集成查询的缩写)也是扩展方法的一大体现

 

LINQ实例

    class Program
    {
        static void Main(string[] args)
        {
            List<int> myList = new List<int>() { 11, 12, 13, 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,确实是扩展方法。

8 总结

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

  • 传值参数:参数的默认传递方法
  • 输出参数:用于除返回值外还需要输出的场景
  • 引用参数:用于需要修改实际参数值的场景
  • 数组参数:用于简化方法的调用
  • 具名参数:提高可读性
  • 可选参数:参数拥有默认值
  • 扩展方法(this 参数):为目标数据类型“追加”方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值