一、传值参数
前面没有修饰符
就是C里边的形参。普普通通,没啥可说。
会创建副本,占用存储位置。
有一点要注意。值类型的变量存储的是数值;但是引用变量存储的是对象的地址,所以如果对传值参数的引用变量操作,是会改变该对象的。但是如果对传值参数赋值一个新对象的地址,原来的实参牵着的对象不会改变。
例子:
//输出结果:10 100 100,主函数的stu所指的对象在调用方法后成员改变。
//两者的hashcode相同。
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Student stu = new Student();
stu.Age = 10;
Console.WriteLine(stu.Age);
Student.haha(stu);
Console.WriteLine(stu.Age);
Console.WriteLine(stu.GetHashCode());
}
}
class Student
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
public static void haha(Student stu)
{
stu.age = 100;
Console.WriteLine(stu.age);
Console.WriteLine(stu.GetHashCode());
}
}
}
//输出结果:10 100 10
//hashcode里面外面不一样,且差别较大:46104728 12289376
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Student stu = new Student();
stu.Age = 10;
Console.WriteLine(stu.Age);
Student.haha(stu);
Console.WriteLine(stu.Age);
Console.WriteLine(stu.GetHashCode());
}
}
class Student
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
public static void haha(Student stu)
{
Console.WriteLine(stu.GetHashCode());
stu = new Student();
stu.age = 100;
Console.WriteLine(stu.GetHashCode());
Console.WriteLine(stu.age);
}
}
}
但实际上,例1这种修改了实参对象,这种结果常常被叫作方法的副作用。平时应该尽量避免。
二、引用参数
ref修饰符
引用参数不创造新的存储位置,他直接表示实参的那个变量所表示的对象存储位置。
变量在可以作为引用形参传递之前,必须先明确赋值!!
//对于值类型:相当于C语言的指针
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
int x = 100;
haha(ref x);
Console.WriteLine(x);
}
static void haha(ref int x)
{
x = 10;
}
}
}
//对于引用类型,不改变牵着的对象,改变对象成员
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Student stu = new Student() { Age = 10 };
IWantSideeffect(ref stu);//此处要写上ref,显式告诉编译器,我就是要改
Console.WriteLine(stu.Age);
}
static void IWantSideeffect(ref Student stu)//感觉老师这个名字写的很好玩于是就搬了过来www
{
stu.Age = 100;
}
}
class Student
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
}
}
//对于引用类型,直接覆盖一个新的对象
/*
输出结果:
10,46104728
10,46104728
100,12289376
100,12289376
显然实参也被改变了。这点可看出引用类型与值类型效果不同。
*/
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
Student stu = new Student() { Age = 10 };
Console.WriteLine("{0},{1}", stu.Age, stu.GetHashCode());
IWantSideeffect(ref stu);
Console.WriteLine("{0},{1}", stu.Age, stu.GetHashCode());
}
static void IWantSideeffect(ref Student stu)
{
Console.WriteLine("{0},{1}",stu.Age,stu.GetHashCode());
stu = new Student() { Age = 100 };
Console.WriteLine("{0},{1}", stu.Age, stu.GetHashCode());
}
}
class Student
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
}
}
三、输出参数
out修饰符
输出参数不会创造变量的副本,所以没有占用新的存储空间。
使用out修饰符显式表示,该方法副作用是向外界变量输出值。
out参数要求一定在各种分支都要有输出。
out参数给予的那个实参之前可以没值。
ref更倾向于改变,out倾向于输出。
//输出类型对于值类型:
class Program
{
static void Main()
{
int x = -100, y = 10,result;
result = 100;//如果不成功,result原本的值会被覆盖
bool isSuccess = OutPut(out result, x, y);
Console.WriteLine("{0},{1}",result,isSuccess);
}
static bool OutPut(out int result,int x,int y)
{
if (x + y > 0)
{
result = x + y;
return true;
}
else
{
result = 0;
return false;
}
}
不过其实我们之前就见过out了:
static void Main()
{
string str = Console.ReadLine();
int result;
bool isSuccess = int.TryParse(str, out result);
Console.WriteLine("{0},{1}",isSuccess,result);
}
//输出类型对于引用类型
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
string name = "Tim";
int age = -20;
Student stu;
bool isSuccess = StudentFactory.CreateStudent(name, age,out stu);
if (isSuccess)
{
Console.WriteLine("{0},{1}",stu.Name,stu.Age);
}
else
{
Console.WriteLine("The age has error!");
}
}
}
class Student
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
class StudentFactory
{
public static bool CreateStudent(string name,int age,out Student result)
{
result = null;//设置返回初值,放在这个位置牛逼啊
if (string.IsNullOrEmpty(name))
{
return false;
}
if (age<0||age>30)
{
return false;
}
//符合规范:今早return。
Student stu = new Student() { Age = age, Name = name };
result = stu;
return true;
}
}
}
四、数组参数
params修饰符
必须是形参列表的最后一个
class Program
{
static void Main()
{
int[] array = new int[] { 1, 2, 3 };
int result = Add(array);
Console.WriteLine(result);
}
static int Add(int[] array)
{
int sum = 0;
foreach (var item in array)
{
sum += item;
}
return sum;
}
}
class Program
{
static void Main()
{
int result = Add(1,2,3);
Console.WriteLine(result);
}
static int Add(params int[] array)
{
int sum = 0;
foreach (var item in array)
{
sum += item;
}
return sum;
}
}
其实我们一直看惯的Console.WriteLine也是如此:
还有一个例子,就是split方法:
static void Main()
{
string str = "Tim,Tom;HH?aa";
string[] strs = str.Split(',', ';', '?');
foreach (var item in strs)
{
Console.WriteLine(item);
}
}
五、具名参数
参数位置不受约束。
class Program
{
static void Main()
{
int m = 10, n = 20;
Program.haha(x: n, y: m);
}
static void haha(int x,int y)
{
Console.WriteLine("{0},{1}",x,y);
}
}
//甚至可以如此地整活
static void Main()
{
int x = 10, y = 20;
Program.haha(x: y, y: x);
}
六、可选参数
参数因具有默认值而变得可选。
不推荐使用哦。。。
class Program
{
static void Main()
{
Program.haha();
}
static void haha(int x=100,int y=20)
{
Console.WriteLine("{0},{1}",x,y);
}
}
七、扩展方法(this参数)
是想对一个类进行扩展,使得该类具有别的类的方法。
比如说:
//对x进行取四位小数
static void Main()
{
double x = 3.112323435;
x = Math.Round(x, 4);
Console.WriteLine(x);
}
感觉这样用下来有点曲折。我们想直接x = x.Round(x,4)看着会简洁明了一些。这时候我们便需要对double这个类进行方法扩展。
namespace ConsoleAppPractice
{
class Program
{
static void Main()
{
double x = Math.PI;
x = x.Round(4);
Console.WriteLine(x);
}
}
static class DoubleExtension
{
public static double Round(this double x,int y)
{
return Math.Round(x, y);
}
}
}
对于扩展方法定义的几个注意事项:
1.必须有static修饰的XXXExtension类来收纳对XXX的扩展方法。
2.方法必须是public且static的,但用的时候以实例对象引用
3.必须有this修饰符,且this参数一定要放在第一个。
举例:LINQ方法:
//检测是否全部大于10
static void Main()
{
List<int> list = new List<int>() { 100,111,234,452,324};
bool result = list.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;
}
·小技巧:
CTRL+。:自动替换变量名