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不支持常量方法和常量参数



posted @ 2007-09-09 18:53  包建强  Views(1136)  Comments(1Edit  收藏  举报