Jianqiang's Silverlight Blog
Focus on PRISM, MVVM, IoC and MMORPG games
CnBlogs
Home
New Post
Contact
Admin
Rss
Posts - 514 Articles - 15 Comments - 3549
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
Jianqiang Bao
Views(443)
Comments(1)
Edit
收藏
Post Comment
890821
回复
引用
查看
#1楼
[
楼主
]
2007-09-12 15:41
|
包建强
使用IList<T>而不是List<T>的理由:
理由1:
面向接口编程。
如果.Net 3.0或4.0,5.0里面,List可能产生一些变化,但是IList却不太可能变化(因为只是个接口,实现又不在这里,变化个接口意义不太大,就算这个接口不合理也是新加一个接口不会去改接口的)。
返回一个IList给了使用上更多灵活性,这个才是更重要的。
理由2:
基本上,IList<T>与List<T>类型的接口功能是完全一样的。
.net不支持多重继承。那么使用IList<T>就能够适应更多的类型,包括不是从List<T>继承的类型。
不过,实际上使用Interface编程是非常令人痛苦的。因此,一般人返回一个这种集合的时候十有八九会从List<T>继承,而不会让自定义的集合仅实现IList<T>。好在List<T>也是IList<T>,所以使用IList<T>完全可以隐藏这种乱七八糟的选择。
综合起来看,IList<T>很难用,但是灵活。
理由3:
我觉得是最小使用原则,如果接受者并不需要具体的容器,而只需要一个可以被当作容器的“东西”,这个时候就不必返回一个具体的容器。
另一个方面也是从语义上来说,IList强调我返回一个容器,List强调我返回一个特定的容器。
面向接口编程不能生搬硬套,要不然也会很痛苦。
最后说明ArrayList和LinkedList等都实现了IList的接口
你可以声明一个IList类型的变量,使之指向ArrayList或者LinkedList
IList list = new ArrayList();
IList list = new LinkedList();
注册用户登录后才能发表评论,请
登录
或
注册
,
返回博客园首页
。
首页
博问
闪存
新闻
园子
招聘
知识库
最新IT新闻
:
·
Chrome将给老机带来更快的3D绘图性能
·
在线支付创业公司Stripe获红杉资本等1800万美元的投资,公司估值达1亿美元
·
创新工场孵化公司磊友科技今天正式推出首款大型手机HTML5网页游戏《黎明帝国》
·
霍金的伟大与不幸
·
Linux为什么成功?因为它的失败是免费的!
»
更多新闻...
最新知识库文章
:
·
高级编程语言的发展历程
·
如何学习一门新的编程语言?
·
学习不同编程语言的重要性
·
为什么我喜欢富于表达性的编程语言
·
计算机专业的女生为什么要学编程
»
更多知识库文章...
China-pub 2011秋季教材巡展
China-Pub 计算机绝版图书按需印刷服务
公告
Name:Jianqiang Bao
Position:Beijing, China
Company:Microsoft
My Writings
《Expert .NET 2.0 IL Assembler》
《博客园精华集——WEB标准之道》
昵称:
Jianqiang Bao
园龄:
5年11个月
粉丝:
180
关注:
0
Search
Post Categories
.NET CLR(39)
(rss)
.NET MSIL(68)
(rss)
ASP.NET 2.0(16)
(rss)
Data Structures(12)
(rss)
Design Patterns(33)
(rss)
Open SourceCode(5)
(rss)
Others(92)
(rss)
Prism(25)
(rss)
SharpDevelop 0.92(11)
(rss)
Silverlight MMORPG(17)
(rss)
SQLServer研究(5)
(rss)
WCF(2)
(rss)
WF(8)
(rss)
WPF(109)
(rss)
包包版网络棋牌大厅(25)
(rss)
博客园精花集(28)
(rss)
Post Archives
2011/10 (2)
2011/8 (1)
2011/3 (1)
2011/2 (6)
2011/1 (1)
2010/6 (2)
2010/5 (1)
2010/4 (2)
2010/3 (5)
2010/2 (17)
2009/12 (6)
2009/11 (27)
2009/10 (24)
2009/9 (3)
2009/8 (6)
2009/7 (15)
2009/6 (5)
2009/5 (8)
2009/4 (2)
2009/3 (14)
2009/2 (5)
2009/1 (20)
2008/12 (10)
2008/11 (7)
2008/10 (10)
2008/9 (15)
2008/8 (14)
2008/7 (28)
2008/6 (10)
2008/5 (15)
2008/4 (58)
2008/3 (26)
2008/2 (8)
2008/1 (1)
2007/10 (31)
2007/9 (30)
2007/8 (9)
2007/7 (7)
2007/5 (3)
2007/4 (36)
2007/3 (20)
2006/11 (4)
Recent Comments