当前位置:   article > 正文

.NET框架- in ,out, ref , paras使用的代码总结 C#中in,out,ref的作用 C#需知--长度可变参数--Params C#中的 具名参数 和 可选参数 DEMO...

.net8 out 不需要定义

C#.net 提供的4个关键字,in,out,ref,paras开发中会经常用到,那么它们如何使用呢? 又有什么区别?

 

1 in

in只用在委托和接口中; 
例子:

1

2

3

4

5

6

7

8

9

10

11

12

       //测试模型

       class Model

        {           

        public int a { get; set; }           

        public Model(int a)

            {               

            this.a = a;

            }

        }//创建3个实例List<Model> modelList= new List<Model>()

{ new Model(1), new Model(4), new Model(6) };//调用foreach接口,试着操作3个实例,赋值为nullmodelList.ForEach(e=>e=null);

 

//查看结果://modelList的取值不变。

分析原因,ForEach的参数是委托函数

1

//ForEach方法:public void ForEach(Action<T> action);//委托声明:public delegate void Action<in T>(T obj);

委托是泛型的,类型T前加了一个关键字in,因为带有关键字in,所以T obj是不能被修改的。

尝试测试:

1

//修改元素e的属性amodelList.ForEach(e=>{e.a*=2;});

结果每个元素都乘以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

public bool Operation(out Model updateMod)

{

    updateMode = new Model(5);   

    try{    

    // my operation

     ...    

     //

     return true;

    }    catch{      //写入日志

      return false;

    }

}

//使用

Model um; //未初始化

bool rtnMsg = Operation(out um); //如果初始化,传值通过reference

//分析:

//返回um,如果rntMsg为ture,则um按照预想逻辑被赋值,

//如果rntMsg为false 则um未按照预想逻辑被赋值。

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

public void reviseModel(int a)

{

  a = 12;

}

 

Model model = new Model(10);

//调用reviseModel

reviseModel(model.a); //model.a仍然=10;by-value

reviseMode(ref model.a); //编译不过,提示ref后的参数不归类与变量

int a;

reviseMode(ref a); //如果不给变量a赋一个初始值,

//编译器也是提示:调用前未被赋值的错误

//因此赋值

int a= model.a; //变量a初始值为10;

reviseMode(ref a);//修改变量a=12;但是model.a的值仍然为10

如何修改对象model中的属性a,将其变为12呢?

1

2

3

4

5

6

//直接将参数设为Model对象,则函数调用时,传值通过by referencepublic void reviseModel(Model md)

{

  md.a = 12;

}

 

reviseModel(model );//传值通过by reference

因此,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#比较

在Java里,HashMap

1

2

3

4

// HashMap<K, V> map;

// K key;

V val = map.get(key);if (val != null) {

  // ...}

但val == null,既可能是该map里尚未有键为该key的键值对,也可能是已经有该键值对了但是其值为null。 
要区分两者,HashMap提供了containsKey()方法。所以正确的写法是这样的:

1

2

3

4

// HashMap<K, V> map;

// K key;if (map.containsKey(key)) {

  V val = map.get(key);

  // ...}

containsKey()跟get()的内部操作几乎是一模一样的,都要做一次hash查找,只是返回了查找结果的不同部分而已。也就是说按照这种“正确写法”来写的话,访问一次HashMap就有双倍开销了。杯具!

C#有许多这种细节设计比Java更贴心。看C#用out关键词如何改进这个问题。

System.Collections.Generic.Dictionary

1

2

3

4

5

6

7

TryGetValue:

Dictionary(TKey, TValue).TryGetValue Method (TKey, TValue) (System.Collections.Generic)public bool TryGetValue(

    TKey key,    out TValue value

)ParameterskeyType: TKey

The key of the value to get.

 

valueType: TValue

利用这个方法,上面的Java代码对应的C#版就可以写成:

1

2

3

4

// Dictionary<TKey, TValue> dict;

// TKey key;

TValue val;if (dict.TryGetValue(key, out val)) {

  // ...}

这就把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

public void GetPlansByInputControl(string planState, string contactno,DatePair dp)

{      

    string planStat = "";           

    switch (planState)

    {               

    case "...":

            planStat = "...";                   

            break;               

            case "...":

            planStat = "...";                   

            break;

    }

        plans = getPlansWithCondition(Convert.ToDateTime(dp.startValue), Convert.ToDateTime(dp.endValue), planStat, contactno);

 

}

调用的getPlansWithCondition方法为

1

2

3

4

5

6

7

8

9

10

11

12

13

private List<MPartPlan> getMPartPlansWithCondition(DateTime dateTime,

DateTime dateEndTime, string planStat, string contactNo)

{            var conditions = new CslSqlBaseSingleTable();

    conditions.AddCondition("RequireStartDate", dateTime, DataCompareType.GreaterOrEqual);

    conditions.AddCondition("RequireStartDate", dateEndTime, DataCompareType.LessOrEqual);

    conditions.AddCondition("OrderCode", contactNo, DataCompareType.Equal);           

    if (!string.IsNullOrEmpty(planStat))

    {

        conditions.AddCondition("PlanState", planStat, DataCompareType.Equal);

    }           

    return _cslMPartPlan.QueryListInSingleTable(typeof(MPartPlan), conditions);

}

}

问题来了,当查询再新加1个字段时,你难道还再重载一个版本吗?

5.2 应用params

1

private List<MPartPlan> getMPartPlansWithCondition(DateTime dateTime, DateTime dateEndTime, string planStat, string contactNo,string newField);

当C#提供了params后,当然不用,直接将getMPartPlansWithCondition改写为如下

1

2

3

4

5

6

7

8

9

10

11

private List<MPartPlan> getMPartPlansWithCondition(params object[] queryConditions);

{

   queryConditions[0]

   queryConditions[1]

   queryConditions[2]

   queryConditions[3]

   queryConditions[4]

   //放到字典中dict

 

   sqlQuery(dict);

}

以后随意添加查询字段,只要修改下这个函数就行了,不用增删重载版本!!!

客户端调用,直接加一个字段就行

1

_bsl.GetPlansByInputControl(field1, field2,field3,field4,field5);

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...
        }


    }
}

 

转载于:https://www.cnblogs.com/cjm123/p/9983645.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/415059
推荐阅读
相关标签
  

闽ICP备14008679号