C#.net 提供的4个关键字,in,out,ref,paras开发中会经常用到,那么它们如何使用呢? 又有什么区别?
1 in
in只用在委托和接口中;
例子:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
分析原因,ForEach的参数是委托函数:
1 |
|
委托是泛型的,类型T前加了一个关键字in,因为带有关键字in,所以T obj是不能被修改的。
尝试测试:
1 |
|
结果每个元素都乘以2,变为2,8,12。可知,可以修改对象的属性。
2 out
out 关键字用法注意:
1)带有out的形参,在函数定义时,return前必须给函数赋一个值。
2)调用函数时,带有out的参数不必赋一个初始值。
3)out形参传值是通过引用(by reference)
out使用场景:
在函数返回多个值时,通常用out 返回其中一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
C#.net中有一类TryParse函数,便是out的另一个重要应用。若感兴趣,请见:透过Parse和TryParse:Try-Parse和Tester-Doer模式
3 ref
ref关键字用于改变参数传递,将by value修改为by reference传值,原来是by reference传递的,加上ref还是不加ref,效果是一样的。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
如何修改对象model中的属性a,将其变为12呢?
1 2 3 4 5 6 |
|
因此,ref关键词使用总结:
ref的话,用于处理值变量,如基本类型、结构等,它们不需要被new出来,传值依照的是值拷贝。
1)ref 后的变量,如果是值类型(value type),那么加上ref后变为按照 by reference传值;
2)ref 后的变量,如果是引用类型(reference type),那么加上ref与不加没有任何区别;
3)ref后的变量,使用前必须赋值
4)ref后的变量不能是引用类型的属性
以上是基本的分析,在使用中就够了,如果想更深入的分析这个问题,请继续。
4 深入探讨out ref
主要分析out ref 到底有何用,不用他们会有什么影响。
1) C#中有一类方法,名字叫作Try…,如Int.TryParse,它返回一个bool值,尝试解析一个字符串,如果成功解析为整数,则返回true,得到的整数作为第二个out的int被传出。
见分析文章
异常设计准则
透过Parse和TryParse:Try-Parse和Tester-Doer模式
从文章中看出,相比没有out参数的次方法Parse,如果解析字符串失败,则会抛出一个参数错误的异常。
用Try…方法写出来的代码比try…catch写出来的要简洁,于是这也变成了out参数使用的一个常用场景。
2) Java和C#比较
1 2 3 4 |
|
但val == null,既可能是该map里尚未有键为该key的键值对,也可能是已经有该键值对了但是其值为null。
要区分两者,HashMap提供了containsKey()方法。所以正确的写法是这样的:
1 2 3 4 |
|
containsKey()跟get()的内部操作几乎是一模一样的,都要做一次hash查找,只是返回了查找结果的不同部分而已。也就是说按照这种“正确写法”来写的话,访问一次HashMap就有双倍开销了。杯具!
C#有许多这种细节设计比Java更贴心。看C#用out关键词如何改进这个问题。
System.Collections.Generic.Dictionary
1 2 3 4 5 6 7 |
|
利用这个方法,上面的Java代码对应的C#版就可以写成:
1 2 3 4 |
|
这就把ContainsKey与Item[Key]的语义结合了起来,把一次hash查找能找到的信息一口气都返回出来,从源头上避免了“两次查找”的冗余操作,有利于程序的性能。
C#.net中提供了一个关键字 params,以前都不知道有这个关键字,有一次,同事看到我的几版重载函数后,淡定地和我说了一句,哥呀,你可以用params,后来查了查,现在经常用习惯了,这不刚才又把之前写的几版都拿掉了,又用params重构了下。
5 Paras
那么,我就把params的用处,我经历的这个过程说一下。
5.1 问题的需求
在客户端,客户经常会变动查询的字段,前几天还是根据4个关键字段去服务器查询几个模型呢,今天,又想加1个查询字段。
根据4个关键字段的查询方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
调用的getPlansWithCondition方法为
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
问题来了,当查询再新加1个字段时,你难道还再重载一个版本吗?
5.2 应用params
1 |
|
当C#提供了params后,当然不用,直接将getMPartPlansWithCondition改写为如下
1 2 3 4 5 6 7 8 9 10 11 |
|
以后随意添加查询字段,只要修改下这个函数就行了,不用增删重载版本!!!
客户端调用,直接加一个字段就行
1 |
|
5.3 总结
queryFun(params object[] objs),带有这个参数的函数,只需要一个版本,这样解决了因为个数不一致而导致的多个重载版本,
在客户端调用时,将属性参数一一列数即可。
C#中in,out,ref的作用
In:过程不会改写In的内容
Out和out:传入的值不会被过程所读取,但过程可以写
ref:传入的值,过程会读,也会写
就象你把布料送到裁缝的一个收料箱(裁缝用这个区别是哪家客户)
IN:这块布料,不能动,我取时还要原样(我取时会要不要这块料,是我自己的事,你管不着,但你不能把这块料做任何改变,你只能看这块料的质地、色彩等等,你要想改变这块料,那自已去照这块料的样子复制一个)
Out和out:我可能给了你布料,也可能没给,也可能我给你的只是一张纸或一块羊皮,但我希望无论我给或没给,你都会给我一件衣服,并放到收料箱中,至于放不放衣服是你的事
ref:这块布料,保证是布料,你可以加工,也可以不加工,但无论你加工或是没加工,都得给我放回收料箱中.
ref
通常我们向方法中传递的是值.方法获得的是这些值的一个拷贝,然后使用这些拷贝,当方法运行完毕后,这些拷贝将被丢弃,而原来的值不将受到影响.此外我们还有其他向方法传递参数的形式,引用(ref)和输出(out).
有时,我们需要改变原来变量中的值,这时,我们可以向方法传递变量的引用,而不是变量的值.引用是一个变量,他可以访问原来变量的值,修改引用将修改原来变量的值.变量的值存储在内存中,可以创建一个引用,他指向变量在内存中的位置.当引用被修改时,修改的是内存中的值,因此变量的值可以将被修改.当我们调用一个含有引用参数的方法时,方法中的参数将指向被传递给方法的相应变量,因此,我们会明白,为什么当修改参数变量的修改也将导致原来变量的值.
创建参数按引用传递的方法,需使用关键字ref.例;
using System;
class gump
{
public double square(ref double x)
{
x=x*x;
return x;
}
}
class TestApp
{
public static void Main()
{
gump doit=new gump();
double a=3;
double b=0;
Console.WriteLine(\"Before square->a={0},b={1}\",a,b);
b=doit.square(ref a);
Console.WriteLine(\"After square->a={0},b={1}\",a,b);
}
}
通过测试,我们发现,a的值已经被修改为9了.
out
通过指定返回类型,可以从方法返回一个值,有时候(也许还没遇到,但是我们应该有这么个方法),需要返回多个值,虽然我们可以使用ref来完成,但是C#专门提供了一个属性类型,关键字为out.介绍完后,我们将说明ref和out的区别.
通过使用out关键字,我们改变了三个变量的值,也就是说out是从方法中传出值.
using System;
class gump
{
public void math_routines(double x,out double half,out double squared,out double cubed)
//可以是:public void math_routines(//ref double x,out double half,out double squared,out double cubed)
//但是,不可以这样:public void math_routines(out double x,out double half,out double squared,out double cubed),对本例来说,因为输出的值要靠x赋值,所以x不能再为输出值
{
half=x/2;
squared=x*x;
cubed=x*x*x;
}
}
class TestApp
{
public static void Main()
{
gump doit=new gump();
double x1=600;
double half1=0;
double squared1=0;
double cubed1=0;
[Page]
/*
double x1=600;
double half1;
double squared1;
double cubed1;
*/
Console.WriteLine(\"Before method->x1={0}\",x1);
Console.WriteLine(\"half1={0}\",half1); Console.WriteLine(\"squared1={0}\",squared1);
Console.WriteLine(\"cubed1={0}\",cubed1);
doit.math_rountines(x1,out half1,out squared1,out cubed1);
Console.WriteLine(\"After method->x1={0}\",x1);
Console.WriteLine(\"half1={0}\",half1);
Console.WriteLine(\"squared1={0}\",squared1);
Console.WriteLine(\"cubed1={0}\",cubed1);
}
}
我们发现,ref和out似乎可以实现相同的功能.因为都可以改变传递到方法中的变量的值.但是,二者本质本质的区别就是,ref是传入值,out是传出值.在含有out关键字的方法中,变量必须由方法参数中不含out(可以是ref)的变量赋值或者由全局(即方法可以使用的该方法外部变量)变量赋值,out的宗旨是保证每一个传出变量都必须被赋值.
上面代码中被/**/注释掉的部分,可以直接使用.也就是说,在调用方法前可以不初始化变量.但是\"x1\"是要赋值的,否则要报错.而ref参数,在传递给方法时,就已经是还有值的了,所以ref侧重修改.out侧重输出.
---------------------
作者:随智阔
来源:CSDN
原文:https://blog.csdn.net/zhongguowangzhan/article/details/78551729
版权声明:本文为博主原创文章,转载请附上博文链接!
重拾C#日常积累:in、ref、out类型标识的方法参数
为不具有 in、ref 或 out 的方法声明的参数会按值传递给调用的方法。 可以在方法中更改该值,但当控制传递回调用过程时,不会保留更改后的值。 可以通过使用方法参数关键字更改此行为。
本部分介绍声明方法参数时可以使用的关键字:
params 指定此参数采用可变数量的参数。
in 指定此参数由引用传递,但只由调用方法读取。
ref 指定此参数由引用传递,可能由调用方法读取或写入。
out 指定此参数由引用传递,由调用方法写入。
参考地址:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/method-parameters
C#需知--长度可变参数--Params
Params用于参数的数量可变的情况下,即参数的个数是未知数。
使用Params需要知道以下几点:
1、如果函数传递的参数含有多个,使用Params标记的参数数组需要放在最后
图上显示的很明确,不需要多解释,只能使用A的那种排序方式
2、Params修饰的一定要是数组,而且必须是一维数组
3、Params不能和ref、out组合使用
具体参见Hunts.C前辈的文章http://www.cnblogs.com/hunts/archive/2007/01/13/619620.html
4、与Params修饰的参数数组对应的实参可以是同一类型的数组名(注意:只能是一个数组名,多个数组名是不可以的),也可以是任意多个与该数组的元素属于同一类型的变量
演示代码
class Program { static void Main(string[] args) { //展示参数是可变的 int i = Sum(1, 13, 23, 34); Console.WriteLine(i); int j = Sum(1, 1, 3, 2, 4, 4, 44, 555, 6); Console.WriteLine(j); //实参可以是数组名 int[] ArrayI = new int[5] { 1, 2, 3, 4, 5 }; int ArraySum = Sum(ArrayI); Console.WriteLine(ArraySum); Console.Read(); } static int Sum(params int[] s) { int sum = 0; foreach(int i in s) { sum += i; } return sum; } }
C#中的 具名参数 和 可选参数
具名参数 和 可选参数 是 C# framework 4.0 出来的新特性。
一. 常规方法定义及调用
public void Demo1(string x, int y) { //do something... } public void Main() { //调用 Demo1("similar", 22); }
调用时,参数顺序(类型)必须与声明一致,且不可省略。
二. 可选参数的声明及调用
可选参数分为两种情况: 1. 部分参数可选; 2. 全部参数都是可选
//部分可选(x为必选,y为可选) public void Demo2(string x, int y = 5) { //do something... } public void Main() { //调用 Demo2("similar"); // y不传入实参时,y使用默认值5 Demo2("similar", 10); // y传入实参,则使用实参10 }
注: 当参数为部分可选时, 可选参数 的声明必须定义在 不可选参数 的后面(如上: y 的声明在 x 之后),不然会出现如下错误提示:
//全部可选(x,y 均为可选参数) public void Demo3(string x = "demo", int y = 5) { //do something... } public void Main() { //调用 Demo3(); // x,y不传入实参时,x,y使用默认值 "demo",5 Demo3("similar"); // y不传入实参时,y使用默认值5 Demo3("similar", 10); // x,y都传入实参 }
注: a. 当参数全部都为可选时,参数的声明顺序可以随意定义,不分先后。
b. 参数声明定义可以无顺序,但调用时必须与声明时的一致。
上面的调用只写的3种,其实还有一种,就是 x 使用默认值,y 传入实参,即 : Demo3(10);
但这样调用会报错,因为Demo3的第一个参数是 string 类型,错误消息如图:
但是现在我只想传入y, 不想传入 x ,该怎么办呢,那么就要用到 C#的 具名参数。
三. 具名参数
具名参数的使用主要是体现在函数调用的时候。
public void Main() { //调用 Demo3(); // x,y不传入实参时,x,y使用默认值 "demo",5 Demo3("similar"); // y不传入实参时,y使用默认值5 Demo3("similar", 10); // x,y都传入实参
// 具名参数的使用 Demo3(y:10); }
通过具名参数,我们可以指定特定参数的值,这里我们通过 Demo3(y:10)就可以解决我们上面遇到的问题(x使用默认值,y使用实参)。
注: 当使用 具名参数时,调用方法可以不用管参数的声明顺序,即如下调用方式也是可以的:
在调用含有可选参数的方法时,vs中会有智能提示,提示哪些是可以选参数及其默认值,中括号表示可选[]:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Cin只读0ref有去有回0out无去有0params可变数量 { //测试模型 class Model { public int a { get; set; } public Model(int a) { this.a = a; } } class Program { #region in 关键字 delegate void DContravariant<in A>(A argumen); static void objFunction(object obj) { Console.WriteLine("你变了"); } static void strFunction(string str) { Console.WriteLine(str); } #endregion static void Main(string[] args) { //in只用在委托和接口中; List<Model> modelList = new List<Model>() { new Model(1), new Model(4), new Model(6) }; modelList.ForEach(e => e = null); Console.WriteLine(); //modelList的取值不变。ForEach的参数是委托函数: //委托是泛型的,类型T前加了一个关键字in,因为带有关键字in,所以T obj是不能被修改的。 //修改 属性a modelList.ForEach(e => { e.a *= 2; }); Console.WriteLine();//结果每个元素都乘以2,变为2,8,12。可知,可以修改对象的属性。 #region in关键字 // in关键字可指定类型参数是逆变的。 // 逆变使你使用的类型可以比泛型参数指定的类型派生程度更小。 DContravariant<object> f1 = objFunction; //DContravariant<string> f1 = objFunction; // 可以使用string类型参数传入in object DContravariant<string> f2 = strFunction; f2 = f1; // string类型可以向object转换。 f2("执行了"); #endregion Console.WriteLine("----"); string refss = "yuanli"; //有进有出 Getref(ref refss); Console.WriteLine(refss); Console.WriteLine("----"); //string outss = null; //string outss = "yuanli"; //赋不赋值 无所谓 方法里面也取不到(取的话 提示错误 使用了未赋值的 out 参数) //代码规范严谨 建议不赋 string outss;// 值为null 和第一种是一样的 Getout(out outss); Console.WriteLine(outss); // Getparams("123", "456", "789"); //一般方法 顺序一致 不可省略 demo("", 0); //可选参数 demokexuan("similar", 10); // 不是 重载方法 demokexuan("similar"); // 没为第二个参数赋值 设了默认值 //Demo3引出具名 Demo3引出具名(); Demo3引出具名("similar"); Demo3引出具名("similar", 10); //Demo3引出具名(10); //只想x 使用默认值,y 传入实参 引出具名 报错 最匹配的重载方法具有一些无效参数 //无法从“int”转换为“string” // 具名参数的使用 Demo3引出具名(y: 10); //注: 当使用 具名参数时,调用方法可以不用管参数的声明顺序,即如下调用方式也是可以的: [] 表示可选 Demo3引出具名(x: "123", y: 10); Demo3引出具名(y: 123, x: "789"); //一般方法 同样可以使用具名参数调用方式调用 demo(str: "123", i: 10); demo(i: 123, str: "891"); } static void Getref(ref string str) { Console.WriteLine("str是什么 " + str); str = "ref修改str"; } static void Getout(out string str) {//out //Console.WriteLine("str是什么 " + str); // 错误 使用了未赋值的 out 参数“str” str = "out赋值str"; Console.WriteLine(str); } static void Getparams(params string[] str) {//params foreach (var item in str) { Console.WriteLine(item); } } static void demo(string str, int i) {// } static void demokexuan(string str, int y = 5) {//kexuan 设置默认值 } //当参数为部分可选时, 可选参数 的声明必须定义在 不可选参数 的后面 //static void demokexuan2(string str = "", int y) //错误 1 可选参数必须出现在所有必需的参数之后 static void demokexuan2(string str, int y = 0) {//kexuan 设置默认值 } //引出具名参数 static void Demo3引出具名(string x = "demo", int y = 5) { //do something... } } }