有关.NET参数传递的方式引发的思考

下面就简单的介绍一下.NET的一些常用参数用法,如有不足还望指正,也欢迎大家在下面留言讨论,分享自己的见解。

一.DotNet参数概述:

.NET中参数(形式参数)变量是方法或索引器声明的一部分,而实参是调用方法或索引器时使用的表达式。

在CLR中,默认的情况下所有的方法参数都是传值的。在传递引用类型的对象时,对一个对象的引用会传递给方法。这里的船引用本身是以传值的方式传给方法的。这也意味着方法能够修改对象,而调用者能看到这些修改。对于值类型的实例,传给方法的实例的一个副本。意味着方法将获得它专用的一个值类型实例副本,调用者中的实例不受影响。

在CLR中允许以传引用而非传值的方式传递参数,在C#中使用out和ref来实现传递引用的方式传值。在C#中使用out和ref来实现传递引用的方式传值,这两个关键字告诉编译器生成元数据来指明该参数是传引用的,编译器将生成代码来传递参数的地址,而不是传递参数本身。为值类型使用out和ref,效果等同于以传值的方式传递引用类型。

常用的参数主要有基本类型参数,泛型参数,以及<in T>和<out T>,dynamic等等。例如<in T>和<out T>,在CLR中支持泛型类型的可变性,C#在4.0时获得了生命泛型遍体所必须的语法,并且现在编译器也能够知道接口和委托可能的转换。可变性是以一种类型安全的方式,讲一个对象作为另一个对象来使用。可变性应用于泛型接口和泛型委托的类型参数中。协变形用于向调用者返回某项操作的值;逆变性是指调用者想API传入值;不变性是相对于协变性和逆变性,是指什么也不会发生。对于这方面的知识非常的丰富,有兴趣的可以自行了解,这里就不做详细的介绍了。dynamic类型,C#是一门静态类型的语言,在某些情况下,C#编译器要寻找特定的名称而不是接口。dynamic可以在编译时做任何事,到执行时再由框架进行处理。有关动态类型的介绍也不做更深入的介绍。

在.NET中参数的使用方法主要为可选参数、命名参数、可变数量参数等等。本文下面也是主要介绍这三种参数的使用方法。

二.DotNet参数用法:

以下是主要介绍三种参数的用法:可选参数;命名实参;传递可变数量的参数。

1.可选参数:

  (1).基本用法:

如果某个操作需要多个值,而有些值在每次调用的时候又往往是相同的,这时通常可以使用可选参数。在C#以前实现可变参数的功能,往往声明一个包含所有可能参数的方法,其他方法调用这个方法,并传递恰当的默认值。

在可选参数中,设计一个方法的参数时,可以为部分或全部参数分配默认值。在调用这些方法代码可以选择不指定部分实参,接受默认值。还可以在调用方法时,还可以通过指定参数名称的方式为其传递实参。如下实例:

 static void OptionalParameters(int x, int y = 10, int z = 20)
  {
   Console.WriteLine("x={0} y={1} z={2}",x,y,z);
  }
 OptionalParameters(1, 2, 3);
   OptionalParameters(1, 2);
   OptionalParameters(1);

以上的例子可以很清楚的看到其用法,int y=10和int z=20这两个参数就是可选参数。可选参数的使用中,如果调用时省略了一个参数,C#编译器会自动嵌入参数的默认值。向方法传递实参时,编译器按从左向右的顺序对实参进行求值。使用已命名的参数传递实参时,编译器仍然按照从左到右的顺序对实参进行求值。

      (2).基本原则:

可选参数包含一些规范,具体的一些要求如下:

    (a).所有可选参数必须出现在必备参数之后,参数数组(使用params修饰符声明)除外,但他们必须出现在参数列表的最后,在他们之前是可选参数。

    (b).参数数组不能声明为可选的,如果调用者没有指定值,将使用空数组代替。

    (c).可选参数不能使用ref和out修饰符。

    (d).可选参数可以为任何类型,但对于指定的默认值却有一些限制,那就是默认值必须为常量(数字或字符串字面量、null、const成员、枚举成员、default(T)操作符)。

    (e).指定的值会隐式转换为参数类型,但是这种转换不能是用户定义的。

    (f).可以为方法、构造器、有参属性的参数指定默认值,还可以为属于委托定一部分的参数指定默认值。

    (g).C#不允许省略逗号之间的实参。

在使用可选参数时,对于引用类型使用null来做默认值,如果参数类型是值类型,只需要使用相应的可空值类型作为默认值。

(3).代码示例:

 /// <summary>
  /// 提取异常及其内部异常堆栈跟踪
  /// </summary>
  /// <param name="exception">提取的例外</param>
  /// <param name="lastStackTrace">最后提取的堆栈跟踪(对于递归), String.Empty or null</param>
  /// <param name="exCount">提取的堆栈数(对于递归)</param>
  /// <returns>Syste.String</returns>
  public static string ExtractAllStackTrace(this Exception exception, string lastStackTrace = null, int exCount = 1)
  {
   while (true)
   {
    var ex = exception;
    const string entryFormat = "#{0}: {1}\r\n{2}";
    lastStackTrace = lastStackTrace ?? string.Empty;
    lastStackTrace += string.Format(entryFormat, exCount, ex.Message, ex.StackTrace);
    if (exception.Data.Count > 0)
    {
     lastStackTrace += "\r\n Data: ";
     lastStackTrace = exception.Data.Cast<DictionaryEntry>().Aggregate(lastStackTrace, (current, entry) => current + $"\r\n\t{entry.Key}: {exception.Data[entry.Key]}");
    }
    //递归添加内部异常
    if ((ex = ex.InnerException) == null) return lastStackTrace;
    exception = ex;
    lastStackTrace = $"{lastStackTrace}\r\n\r\n";
    exCount = ++exCount;
   }
  }

2.命名实参:

以上讲解了可选参数的一些基本概念和用法,接下来看一下命名参数的相关操作用法:

(1).基本用法:

命名实参是指在指定实参的值时,可以同时指定相应的参数名称。编译器将判断参数的名称是否正确,并将指定的值赋给这个参数。命名参数在各个实参之前加上它们的参数名称以及一个冒号。如下代码:

new StreamWriter(path:filename,aooend:true,encoding:realEncoding);

如果要对包含ref和out的参数指定名称,需要将ref和out修饰符放在名称之后,实参之前。

int number;
bool success=int.TryParse("10",result:out number);

      (2).基本原则:

在命名参数中,所有的命名参数必须位于位置实参之后,两者之间的位置不能改变。位置实参总是指向方法声明中相应的参数,不能跳过参数之后,在通过命名相应位置的实参来指定。实参仍然按编写顺序求值,即使这个顺序有可能会不同于参数的声明顺序。

在一般情况下,可选参数与命名实参会一起配合使用。可选参数会增加适用方法的数量,而命名实参会减少使用方法的数量。为了检查是否存在特定的适用方法,编译器会使用位置参数的顺序构建一个传入实参的列表,然后对命名实参和剩余的参数进行匹配。如果没有指定某个必备参数,或某个命名实参不能与剩余的参数相匹配,那么这个方法就不是适用的。

命名实参有时可以代替强制转换,来辅助编译器进行重载决策。如果方法是从模块的外部调用的,更改参数的默认值是具有潜在的危险的。可以按名称将实参传给没有默认值的参数,但是编译器要想编译代码,所有要求的实参都必须传递。

在写C#代码与COM对象模型进行互操作时,C#的可选参数和命名参数功能是最好用的,调用一个COM组件时,为了以传引用的方式传递一个实参,C#还允许省略REF/OUT,在嗲用COM组件时,C#要求必须向实参应用OUT.REF关键字。

3.传递可变数量的参数:

在项目开发中,有时我们需要定义一个方法来获取可变数量的参数。可以使用params,params只能应用于方法签名中的最后一个参数。params关键字告诉编译器向参数应用System.ParamArrayAttribute的实例。我们具体看一下实现的代码:

[AttributeUsage(AttributeTargets.Parameter, Inherited=true, AllowMultiple=false), ComVisible(true), __DynamicallyInvokable]
public sealed class ParamArrayAttribute : Attribute
{
 // Methods
 [__DynamicallyInvokable]
 public ParamArrayAttribute();
}
[__DynamicallyInvokable]
public ParamArrayAttribute()
{
}

以上的代码可以看出该类继承自Attribute类,对于Attribute类可能不会陌生,那就是定义定制属性的基类,说明ParamArrayAttribute类用于定义定制属性,ParamArrayAttribute类在System命名空间下,ParamArrayAttribute类只有一个构造方法,没有具体的实现。AttributeUsage也定义了属性的使用方式。

C#编译器检测到一个方法调用时,会检查所有具有指定名称、同时参数没有应用ParamArrayAttribute的方法。如果找到一个匹配的方法,编译器生成调用它所需的代码。如果编译器没有找到一个匹配的方法,会直接检查应用ParamArrayAttribute的方法。如果找到一个匹配的方法,编译器会先生成代码来构造一个数组,填充它的元素,再生成代码来调用选定的方法。

调用一个参数数量可变的方法时,会造成一些额外的性能损失,数组对象必须在对上分配,数组元素必须初始化,而且数组的内存最终必须垃圾回收。

提供一个方法代码,仅供参考:

 /// <summary>
  /// 字符型二维数组转换成DataTable
  /// </summary>
  /// <param name="stringDyadicArray"></param>
  /// <param name="messageOut"></param>
  /// <param name="dataTableColumnsName"></param>
  /// <returns></returns>
  public DataTable DyadicArrayToDataTable(string[,] stringDyadicArray, out bool messageOut,
   params object[] dataTableColumnsName)
  {
   if (stringDyadicArray == null)
   {
    throw new ArgumentNullException("stringDyadicArray");
   }
   var returnDataTable = new DataTable();
   if (dataTableColumnsName.Length != stringDyadicArray.GetLength(1))
   {
    messageOut = false;
    return returnDataTable;
   }
   for (var dataTableColumnsCount = 0;dataTableColumnsCount < dataTableColumnsName.Length;dataTableColumnsCount++)
   {
    returnDataTable.Columns.Add(dataTableColumnsName[dataTableColumnsCount].ToString());
   }
   for (var dyadicArrayRow = 0; dyadicArrayRow < stringDyadicArray.GetLength(0); dyadicArrayRow++)
   {
    var addDataRow = returnDataTable.NewRow();
    for (var dyadicArrayColumns = 0; dyadicArrayColumns < stringDyadicArray.GetLength(1);dyadicArrayColumns++)
    {
     addDataRow[dataTableColumnsName[dyadicArrayColumns].ToString()] = stringDyadicArray[dyadicArrayRow, dyadicArrayColumns];
    }
    returnDataTable.Rows.Add(addDataRow);
   }
   messageOut = true;
   return returnDataTable;
  }

以上给出了一个使用可变参数数量以及命名参数的使用样例,完成了将二维字节数组转化为DataTable对象,将数组进行遍历,并将数组写入datatable中,对于整个方法的逻辑就不做深入介绍,代码比较的简单。

三.与参数有关的一些指导原则:

声明方法的参数类型时,应尽量指定最弱的类型,最好是接口而不是基类。

在设计模式的基本原则中,迪米特法则也较最少知识原则,迪米特法则是指如果两个类不必彼此直接通信,那么这两个类就不应当直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。在类结构的设计上,每一个类都应当尽量降低成员的访问权限。类之间的耦合度越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。

对于参数的使用中,我们在对参数类型的使用上,还是需要很仔细和认真的去思考,因为在参数类型的定义上,在一定程度上影响着我们程序的扩展性和稳定性,如果参数类型的约束比较大,对于后续方法的扩展,意义是巨大的。在整个面向对象的语言体系中,一切设计模式都是由“多态”延伸而来,对于接口和委托都是在我们面向对象设计中使用很多的,目的较多的是在使用时扩大参数的约束性。

在方法的返回值类型中,返回的类型应该声明为最强的类型,以免受限于特定的类型。

四.总结:

以上是一篇简单介绍方法参数的文章,在文章内容中主要对于介绍可选参数、命名参数等。以上的内容如果有不足的地方还望大家多多包涵,也希望能够指出对应的问题。知识先于模范,后于反思。学习完一点后,需要我们去总结和反思,其中的内涵我们才会有时间和精力,以及由能力去思考。

以上所述是小编给大家介绍的有关.NET参数传递的方式引发的思考,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • asp.net中使用cookie传递参数的方法

    本文实例讲述了asp.net中使用cookie传递参数的方法.分享给大家供大家参考.具体如下: //传值 HttpCookie cookie = new HttpCookie("mycookie"); cookie.Value = "cookie值"; Response.AppendCookie(cookie); Response.Redirect("index.aspx"); //接收 Request.Cookies["mycooki

  • asp.net(C#)函数对象参数传递的问题

    复制代码 代码如下: class Program { static void Main(string[] args) { TestClass objA = new TestClass(); objA.Name = "I am ObjA"; Console.WriteLine(String.Format("In Main:{0}", objA.Name)); TestFun(objA); Console.WriteLine(String.Format("In

  • C#和asp.net中链接数据库中参数的几种传递方法实例代码

    复制代码 代码如下: #region 参数传递方法第一种     //参数设置方法(第一种) //SqlParameter sp = new SqlParameter("@Name", str_Name); //SqlParameter sp2 = new SqlParameter("@Pwd", str_Pwd); //cmd.Parameters.Add(sp); //cmd.Parameters.Add(sp2); #endregion //简单的一般使用第一

  • ASP.NET 页面之间传递参数方法汇总

    在撰写之前假设第一个页面为send.aspx,第二个页面为receive.aspx 1.通过URL链接地址传递 (1) send.asp代码 复制代码 代码如下: protected void Button1_Click(object sender, EventArgs e) { Request.Redirect("Default2.aspx?username=honge"); } (2) receive.aspx代码 复制代码 代码如下: string username = Requ

  • asp.net 页面之间传递参数的几种方法

    第一种方法: 通过URL链接地址传递 send.aspx: 复制代码 代码如下: protected void Button1_Click(object sender, EventArgs e) { Request.Redirect("Default2.aspx?username=honge"); } receive.aspx: 复制代码 代码如下: string username = Request.QueryString["username"];这样可以得到参数

  • 有关.NET参数传递的方式引发的思考

    下面就简单的介绍一下.NET的一些常用参数用法,如有不足还望指正,也欢迎大家在下面留言讨论,分享自己的见解. 一.DotNet参数概述: .NET中参数(形式参数)变量是方法或索引器声明的一部分,而实参是调用方法或索引器时使用的表达式. 在CLR中,默认的情况下所有的方法参数都是传值的.在传递引用类型的对象时,对一个对象的引用会传递给方法.这里的船引用本身是以传值的方式传给方法的.这也意味着方法能够修改对象,而调用者能看到这些修改.对于值类型的实例,传给方法的实例的一个副本.意味着方法将获得它专

  • 关于ORACLE通过file_id与block_id定位数据库对象遇到的问题引发的思考

    在ORACLE中,我们可以通过file_id(file#)与block_id(block#)去定位一个数据库对象(object).例如,我们在10046生成的trace文件中file#=4 block#=266 blocks=8,那么我可以通过下面两个SQL去定位对象 SQL 1:此SQL效率较差,执行时间较长. SELECT OWNER, SEGMENT_NAME, SEGMENT_TYPE, TABLESPACE_NAME FROM DBA_EXTENTS WHERE FILE_ID =&F

  • PHP函数参数传递的方式整理

    在调用函数时,需要向函数传递参数,被传入函数的参数称为实参,而函数定义的参数称为形参.而向函数传递参数的方式有四种,分别是值传递.引用传递.默认参数和可变长度参数. 1. 值传递 值传递是 PHP 中函数的默认传值方式,也称为"拷贝传值".顾名思义值传递的方式会将实参的值复制一份再传递给函数的形参,所以在函数中操作参数的值并不会对函数外的实参造成影响.因此如果不希望函数修改实参的值,就可以通过值传递的方式. [示例]下面定义一个简单的函数,函数有两个参数,在函数中交换参数的值. <

  • JavaScript中实现new的两种方式引发的探究

    前言 当你 new 一个构造函数时发生了什么? "众所周知"的三步: 创建一个空对象,将它的引用赋给 this,继承函数的原型:通过 this 将属性和方法添加至这个对象:最后返回 this 指向的新对象,也就是实例. 一般来说在js中大概是这样的: function Mynew(parent,...rest){ let obj={}; obj.__proto__=parent.prototype; let res=parent.apply(obj,rest); return type

  • 基于Log4j2阻塞业务线程引发的思考

    目录 问题描述 问题1 问题2:异常线程栈打印使用讨论 ThrowableProxy使用错误的CCL原因分析 异步Appender追加日志 创建log4j日志事件 创建ThrownProxy代理 为什么同一个类会加载多次? GeneratedMethodAccessor类 问题总结 问题1 问题2 问题描述 问题1 异步日志打印在ringbuffer满了之后2.7版本的log4j2会默认使用当前线程进行打印日志. 即使不使用默认的策略,2.9之后已经改为默认的为enqueue方式,也会因为最后队

  • php 无法加载mysql的module的时候的配置的解决方案引发的思考

    之后看phpinfo() 里 确实也没找到mysql 模块, 之后所谓的解决方案如"将php.ini" 放入C:\Windows 环境变量等不靠谱说法..... 甚至拷贝ext的文件夹的dll 到System32 中等 统统不靠谱 直到看到这篇帖子,我才发现问题 http://www.haosblog.com/index.php?mod=article_read&id=322 "mysql无法找到的原因是mysql的运行库无法找到,打开mysql的安装文件夹,在bin

  • 基于React.js实现原生js拖拽效果引发的思考

    一.起因&思路 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖拽效果. 首先,其实拖拽效果的思路是很简单的.主要就是三个步骤: 1.onmousedown的时候,启动可拖拽事件,记录被拖拽元素的原始坐标参数. 2.onmousemove的时候,实时记录鼠标移动的距离,结合被拖拽元素第一阶段的坐标参数,计算并设置新的坐标值. 3.onmouseup的时候,关闭可拖拽事件,记录新的坐标值. 注意:这里主要是通过绝对定位的top和left来确定元素的位置

  • javascript 一段代码引发的思考第1/2页

    在2008年的最后一天,在此祝愿大家元旦快乐!!! 郑重声明:此问题根本不是问题,现在看来就是本人知识匮乏,庸人自扰,望广大朋友勿喷!! 细心发现问题,耐心解决问题,信心面对问题. 作者:白某人 长话短说:"服务员,上代码...." 测试代码: function init(){ var tpl = new Ext.Template(' this is div{id} '); tpl.append('div1',{id:'2'}); tpl.insertAfter('div2',{id:

  • 由php的call_user_func传reference引发的思考

    问题的提出 网友bercmisir在院内留言,针对php手册中的call_user_func函数的文档一事,大致如下: http://php.net/manual/en/function.call-user-func.php 其中parameter下有这样一句话: Note: Note that the parameters for call_user_func() are not passed by reference. 简单地翻译一下,是说这个函数的参数是不能依靠引用来传递的. 还有一个例子

  • 关于C语言除0引发的思考

    复制代码 代码如下: <SPAN style="BACKGROUND-COLOR: rgb(241,254,221)"><SPAN style="FONT-FAMILY: Microsoft YaHei">    进行浮点数编程时,如果没有注意,常常会出现输出类似 1.#IND, 1.#INF 或者 nan, inf 之类奇怪的输出.这通常隐含了浮点数操作的异常.</SPAN></SPAN> 进行整数除0的时候,程序会

随机推荐