zoukankan      html  css  js  c++  java
  • CLR笔记:8.方法

    1.实例构造器ctor(引用类型)
        创建引用类型的实例时的步骤:
            首先,为实例的数据字段分配内存;
            接着,初始化对象的系统开销字段(类型对象指针和同步块索引);
            最后,调用类型的实例构造器设置对象的初始状态。

         ctor不能被继承,不能用virtual,new,override,sealed,abstract。

        如果类中没有显示定义任何ctor,则默认定义一个无参ctor,这个ctor不执行任何语句,只是调用基类的无参ctor;当然,如果类中有ctor,则不存在这个默认的无参ctor。
        如果类为abstract的,则默认的ctor是protected;否则这个默认ctor都是public的。
        如果类为static的,则不会生成默认的ctor。
        
        如果基类A中没有提供无参ctor,这里,我们只考虑A中只有一个有参ctor(如果连这个ctor也没有,那么A就是上面的那种情况了),这时,子类B必须显示调用基类的ctor,否则不能编译,如下所示:
        public class A
        
    {
            
    public A(int t)
            

            
            }

        }


        
    public class B : A
        
    {
            
    public B(int t)  : base(t)        //必须显示调用
            
    {

            }

        }


    最终,都会调用到System.Object的公有无参ctor。

    不需要实例构造器的时候:反序列化;Object.MemberwiseClone()方法。
    内联方法,其实就是在默认无参ctor中赋值。

    2.实例构造器ctor(值类型)
        1.C#中,struct不能包含显示的无参ctor——CLR允许struct有无参ctor
        2.struct一定要显示调用其有参ctor,这样在new新对象的时候,才会执行ctor中的语句
        3.CLR会保证所有实例字段(值类型和引用类型)都被初始化为0或者null (如果没有手动设置,就由CLR自动分配)。
        4.在struct中,不能使用内联直接给字段赋值(初始化只能在显示ctor中做)
        5.每个字段都要在ctor中初始化,不然编译器会报错

    3.静态构造器cctor
    cctor可以用于接口/引用类型/值类型,但是C#不支持接口。
    cctor位于AppDomain中,在类第一次被访问时执行。
    cctor不能超过一个(可以没有),而且永远没有参数。
    cctor是私有的,但不能显示声明为私有。
    在值类型中定义cctor是没有意义的——没有机会执行cctor。
    cctor的调用过程:
        编译时,JIT编译器将检查AppDomain是否执行了cctor(有记录的),以决定是否生成调用代码;
        执行时,如果有多个线程,则需要一个互斥的线程同步锁,以确保只执行一次cctor。
    避免在两个类的cctor中互相引用,CLR不能确保先执行哪一个cctor,可以编译,但是返回指不唯一。
    如果cctor抛出异常,CLR会认为该类型不可用,与此类型相关的操作都会抛出System.TypeInitializationException异常。
    cctor中只能访问静态字段,它的用途就是初始化这些静态字段。也可以使用内联初始化,与cctor效果一样。
    cctor不应调用其基类的cctor,两者没有关系。
    CLR不支持静态的Finalize()方法。

    cctor的性能:
    精确语义Precise:针对在cctor中初始化而言;
    字段初始化前语义BeforeFieldInit:针对于内联初始化而言。
    二者的区别在于是否会在MSIL中的cctor上附加一个BeforeFieldInit标记。
    测试代码如下:
        class Program
        
    {
            
    static void Main()
            
    {
                
    const Int32 iterations = 1000 * 1000 * 1000;
                PrefTest1(iterations);
                PrefTest2(iterations);

                Console.ReadLine();
            }


            
    private static void PrefTest1(Int32 iterations)
            
    {
                Stopwatch sw 
    = Stopwatch.StartNew();
                
    for (Int32 i = 0; i < iterations; i++)
                
    {
                    BeforeFieldInit.s_x 
    = 1;
                }

                Console.WriteLine(
    "PrefTest1: {0} BeforeFieldInit", sw.Elapsed);

                sw 
    = Stopwatch.StartNew();
                
    for (Int32 j = 0; j < iterations; j++)
                
    {
                    Precise.s_x 
    = 1;
                }

                Console.WriteLine(
    "PrefTest1: {0} Precise", sw.Elapsed);
            }


            
    private static void PrefTest2(Int32 iterations)
            
    {
                Stopwatch sw 
    = Stopwatch.StartNew();
                
    for (Int32 i = 0; i < iterations; i++)
                
    {
                    BeforeFieldInit.s_x 
    = 1;
                }

                Console.WriteLine(
    "PrefTest2: {0} BeforeFieldInit", sw.Elapsed);

                sw 
    = Stopwatch.StartNew();
                
    for (Int32 j = 0; j < iterations; j++)
                
    {
                    Precise.s_x 
    = 1;
                }

                Console.WriteLine(
    "PrefTest2: {0} Precise", sw.Elapsed);
            }

        }


    *Stopwatch类,提供一组方法和属性,可以准确地测量运行时间

    4.操作符重载
    CLR不知道操作符是什么;操作符是C#定义的,相应的生成编译器识别的语言。
    操作符重载是public static的,有一元重载和二元操作两种方式。
    要求重载方法的参数至少有一个参数与重载方法的类型一样。
    运算符参数不能使用ref/out修饰符。

    例子:对于运算符+,
        在定义时,会在编译器中生成op_Addition()方法,在方法定义表中,这个方法属于specialname组——说明它是一个特殊方法
        在调用时,编译器会在specialname组查找相应的op_Addition()方法,如果存在而且方法参数匹配,则执行;否则,编译错误。

    对于操作符重载,建议同时定义一个友好的方法,如重载+,同时定义一个Add方法。

    操作符语法详见http://www.cnblogs.com/Jax/archive/2007/09/13/891984.html

    5.转换操作符方法
    System.Decimal是一个很好的学习操作符重载/转换的Sample
    为一个Rational定义转换符构造器和方法:
    操作符转换也是public static的
        public sealed class Rational
        
    {
            
    public Rational(Int32 num)
            
    {
                
    //由Int32构建一个Rational
            }


            
    public Rational(Single num)
            
    {
                
    //由Single构建一个Rational
            }


            
    public Int32 ToInt32()
            

                
    //将Rational转换为Int32
            }


            
    public Single ToSingle()
            
    {
                
    //将Rational转换为Single
            }


            
    //将Int32隐式转为Rational,回调Rational(Int32)构造器
            public static implicit operator Rational(Int32 num)
            
    {
                
    return new Rational(num);
            }


            
    //将Single隐式转为Rational,回调Rational(Single)构造器
            public static implicit operator Rational(Single num)
            
    {
                
    return new Rational(num);
            }


            
    //将Rational显式转为Int32,回调ToInt32()
            public static explicit operator Int32(Rational r)
            
    {
                
    return r.ToInt32();
            }


            
    //将Rational显式转为Single,回调ToSingle()
            public static explicit operator Single(Rational r)
            
    {
                
    return r.ToSingle();
            }

        }

    由上面代码可以看出,implicit/explicit是对两个构造器和两个ToXXX()方法的包装。
    相应的IL代码:
            public static Rational op_Implicit(Int32 num)
            
    public static Rational op_Implicit(Single num)
            
    public static Int32 op_Explicit(Rational r)
            
    public static Single op_Explicit(Rational r)
    第3个和第四个方法仅仅是返回类型不同,这样的语法只有在IL中允许。可以看到,操作符转换实际上是在利用IL的这一特性。

    6.通过引用向方法传递参数
        默认CLR的方法参数都是按值传递的。无论引用还是值类型参数,都是传递一个copy的副本——这样意味着方法可以修改对象,而不对方法外的对象有影响,仅在方法内部有影响。
        CLR允许按照引用方式传递参数:out,ref,在声明和调用时都要加上ref/out关键字
            二者在CLR中等效,在C#中有区别:
                不能将未初始化的参数作为ref传递到方法
            //正确使用ref
            static void Main()
            
    {
                
    int x = 2;

                GetRef(
    ref x);
            }


            
    private static void GetRef(ref int x)
            

                x 
    ++;
            }

            //错误使用ref
            static void Main()
            
    {
                
    int x;  //未初始化,即使方法中赋值也不行

                GetRef(
    ref x);
            }


            
    private static void GetRef(ref int x)
            

                x 
    = 10;
            }

                在out方法中必须给out参数初始化赋值,这时,无论调用前是否给out参数初始化赋值,out方法中都要赋值,否则会编译错误。示例如下:
            //正确使用out
            static void Main()
            
    {
                
    //以下两句话都是对的
                int x;
                
    int x = 10;

                GetOut(
    out x);
            }


            
    private static void GetOut(out int x)
            

                x 
    = 10;     //一定要初始化,即使调用前已经初始化了
            }


            //错误使用out
            static void Main()
            
    {
                
    int x = 10;

                GetOut(
    out x);
            }


            
    private static void GetOut(out int x)
            

                x 
    ++;     //一定要初始化,即使调用前已经初始化了
            }

    方法可以基于ref/out可以重载,ref/out也算是方法签名的一部分。但是重载不能仅限于ref和out的差别,如下:
    可以有
        public sealed class Point
        
    {
            
    static void Add(Point p);
            
    static void Add(ref Point p);
        }
    不能有
        public sealed class Point
        
    {
            
    static void Add(Point p);
            
    static void Add(ref Point p);
            
    static void Add(out Point p);
        }

    用ref实现交换两个引用类型的方法:标准写法,使用泛型
            public static void Swap<T>(ref T a, ref T b)
            
    {
                T t 
    = b;
                b 
    = a;
                a 
    = t;
            }


            
    public static void SomeMethod()
            
    {
                String s1 
    = "Jax.Bao";
                String s2 
    = "Fish.Xu";

                Swap(
    ref s1, ref s2);
            }

    out/ref在值类型上使用和引用类型上使用行为相同。仅差在值类型实例分配到的是内存,引用类型分配到的是指针。

    以后的FileStream可以写成以下模式:
            static void Main()
            
    {
                FileStream fs 
    = null;

                ProcessFiles(
    ref fs);

                
    for (; fs != null; ProcessFiles(ref fs))
                

                    fs.Read(.);            
                }

            }



            
    private static void ProcessFiles(ref FileStream fs)
            
    {
                
    if (fs != null)
                
    {
                    fs.Close();
                }


                
    if (noMoreFileToProcess)
                
    {
                    fs 
    = null;
                }

                
    else
                

                    fs 
    = new FileStream(.);
                }

            }


    7.向方法传递可变数量的参数 params关键字
    只有最后一个参数可以是params的,而且必须是一个一维数组,可以传递null或0个元素的数组,甚至是不传值(忽略此参数,会默认生成0长的数组作为方法参数)。
    调用的时候,不用创建数组对象,直接在方法中传入任意数量的参数。
            static void Main()
            
    {
                Console.WriteLine(TestParams());

                Console.WriteLine(TestParams(
    "1""2"));
            }


            
    public static int TestParams(params string[] p1)
            
    {
                
    return p1.Length;
            }


    8.声明方法的参数类型
    原则1:方法参数尽可能指定为最弱的类型
        选用IEnumberable<T>,而不是IList<T>或ICollection<T>
    原则2:方法返回类型尽可能指定为最强的类型
        返回FileStream,而不是Stream
    原则3:如果希望在不影响调用代码的情况下,能对方法内部实现进行修改,这时候返回类型要使用最弱类型中的最强的一个。
        使用IList<T> ,而不是List<T>

    9.CLR不支持常量方法和常量参数



  • 相关阅读:
    Shell 字符串处理
    Shell 变量替换及测试
    ARTS(一)
    instanceof & isAssignableFrom的异同
    mysql 分组排序取最值
    guava-retrying 源码解析(阻塞策略详解)
    guava-retrying 源码解析(时间限制策略)
    guava-retrying 源码解析(停止策略详解)
    guava-retrying 源码解析(等待策略详解)
    guava-retrying 源码解析(导入项目)
  • 原文地址:https://www.cnblogs.com/Jax/p/887609.html
Copyright © 2011-2022 走看看