关于.NET参数传递方式的思考

   年关将近,整我的已经没有了工做和写做的激情,估计这个时候不少人跟我差很少,该相亲的相亲,该聚会喝酒的聚会喝酒,总之就是没有了干活的心思(我有不少想法,但就是叫不动个人手脚,因此我只能看着别人在作我想作的事,吃我想吃的东西。)。本人由上个月的每周四五篇,到如今的文章缩短到每周一篇,说个实话,如今的一篇也有不想写的心思了(这一篇仍是咬着牙写的,感受实在是写不动了,写博客太折腾人了,谁写谁知道啊!),不过仍是但愿写出来能够帮到你们,若有写的不足的地方,还望你们多多指正,知识在于总结和反思,对别人也对本身都是一个提升。  设计模式

   这里先来一段废话,缓和一下气氛,省得让你们很尴尬(太直接了仍是不太好,总不能见到喜欢的女生就表白吧,还得多多的相处,让人以为你稳重有深度。),如今进入咱们今天的博客内容,那就是.NET的参数用法。由于在.NET的参数用法和约束特别多,对于不少初学者来讲,这样繁多的参数用户简直就是跟扯淡同样,即便对因而拥有丰富经验的开发者来讲,也未必可以很轻松使用全部的参数用法和选择合适的参数类型。谈到参数,估计不少人就只是想着咱们在通常的方法调用中使用的那样,如string,int,object等等类型,更多的也就没有了印象,就是知道,也就是在遇到了再去查看一下,这样其实也没错,毕竟不能话费过多的时间用在哪些不经常使用的知识上,可是我我的以为对于知识仍是须要提早有一个全面的学习,可能具体的细节不能很好的把握,可是对于全局的概念仍是得有一个总体的学习。数组

   下面就简单的介绍一下.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能够在编译时作任何事,到执行时再由框架进行处理。有关动态类型的介绍也不作更深刻的介绍。this

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

二.DotNet参数用法:

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

   1.可选参数:

     (1).基本用法:code

        若是某个操做须要多个值,而有些值在每次调用的时候又每每是相同的,这时一般可使用可选参数。在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中,对于整个方法的逻辑就不作深刻介绍,代码比较的简单。

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

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

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

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

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

四.总结:

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

相关文章
相关标签/搜索