构造函数的那些事
如果你不为类定义一个构造函数,他将在编译时候自动产生
public class sometype {
}
public class sometype {
public sometype() : base() { }
}
两段代码的编译结果一样。
如果父类是一个抽象类,那么对父类构造函数的访问权限是 protected。
否则是public。如果父类没有提供一个无参构造函数,那么子类必须显示调用父类的构造函数,否则会报错。如果父类是静态的或者是抽象类的,编译器将不对产生默认的构造函数。
一个类可以定义多个构造函数,但必须得有不同的签名。
一个雷的构造函数必须实现基类的构造函数,否则基类的字段将
不可以访问,(因为字段往往在构造函数中初始化)。所以如果你不显示
调用,c#编译器将会为了自动生成调用一个。到最终来到
system.object中,因为其没有字段,也就没有做什么事。直接返回
有时候一个类的创建不用构造函数的来。
比如使用object’s memberwiseclone。
比如使用反序列化出一个对象(json/xml等)。
需要注意的:不用在构造函数中调用虚方法,因为如果子类复写的
虚方法中用到还没初始化的字段,将产生不可预料的行为。
看下面一个例子
internal sealed class sometype {
private int32 m_x = 5;
}.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// code size 14 (0xe)
.maxstack 8
il_0000: ldarg.0
il_0001: ldc.i4.5
il_0002: stfld int32 sometype::m_x
il_0007: ldarg.0
il_0008: call instance void [mscorlib]system.object::.ctor()
il_000d: ret
}
从il中我们知道sometype 的构造函数中包含
把m_x赋值5的代码 也包含 调用父类 object构造函数的方法。
这说明c#编译器允许你使用内联的方式初始化实例的字段并且放进构造函数中。
internal sealed class sometype {
private int32 m_x = 5;public sometype(){
m_x=1;
}
}
对于上面的代码是不规范和不安全的,更好的写法是
internal sealed class sometype {
private int32 m_x ;public sometype(){
m_x=5;
}
}
值类型实例的函数
值类型不需要构造函数也不会被默认添加,但是你可以为他定义带参构造函数。
结构体被使用后中变量被初始化为0/null.值类型可以直接赋值,当然也可以
使用构造函数赋值,但其构造函数不能为无参构造函数否则会报错
"error cs0568: structs cannot contain explicit parameterless
constructors."
c#编译器这么做是为了让开发人员在构造函数调用这里少些困惑。
而因为值类型没有无参构造函数那么如下代码也一样不能执行(上)篇已经说了。
internal struct somevaltype {
// you cannot do inline instance field initialization in a value type
private int32 m_x = 5;
}
类型的构造函数
clr不仅支持实例的构造函数,还支持 类型构造函数( static 构造函数 。类和机构体的构造函数),甚至是接口的构造函数(c#不支持)。
引用类型的构造函数
internal sealed class somereftype {
static somereftype() {
// this executes the first time a somereftype is accessed.
//首次进入此类型时执行
}
}
值类型的构造函数
internal struct somevaltype {
// c# does allow value types to define parameterless type constructors.
static somevaltype() {
// this executes the first time a somevaltype is accessed.
}
}
当值类型的构造函数并未执行
internal sealed class somereftype {
static somereftype() {
console.writeline("引用类型 的构造函数被 执行 ");
}
}
internal struct somevaltype {
public static int _testint;
static somevaltype() {
console.writeline("值类型 的构造函数被 执行 ");
}
}
class program {
static void main(string[] args) {
somereftype s = new somereftype();
somevaltype[] svt = new somevaltype[1];
console.read();
}
}
测试发现,当使用实例构造函数的时候才会执行结构体里的类型构造函数。
另一个测试代码:
internal sealed class somereftype {
public static int _testint;
static somereftype() {
_testint = 1;
console.writeline("引用类型的类型构造函数被执行");
}
public somereftype() {
console.writeline("引用类型的实例的构造函数被执行");
}
}
internal struct somevaltype {
public static int testint;
static somevaltype() {
console.writeline("值类型的类型构造函数被执行 ");
}
private string test;
public somevaltype(string value) {
console.writeline("值类型实例的构造函数被执行" + value);
test = value;
}
}
class program {
static void main(string[] args) {
display(0);
somereftype s = new somereftype();
display(1);
somereftype s2 = new somereftype();
display(2);
somevaltype[] svt = new somevaltype[1];
display(3);
svt[0] = new somevaltype("test");
display(4);
console.read();
}
static void display(object ob) {
console.writeline(datetime.now.tostring() +" "+ ob.tostring());
}
}
以及他们的执行顺序
另外:由于类型的构造函数在程序中只会执行一次。所以可以利用它来做单例模式
clr并不知道发生了操作符重载这回事,因为在编译的过程中
各种操作符都被生产了对应的代码。比如说+被生产为一个加法函数
public sealed class complex {
public static complex operator+(complex c1, complex c2) {
//to do
}
}
自己动手为 类a重载一个操作符
public class classa {
public static int operator +(classa c1, int c2) {
return ++c2;
//to do
}
}static void main(string[] args) {
int a = 0;
int b = 0;
int c = 0;
c = a + b;
classa ca = new classa();
console.writeline(ca + 1);
console.read();
}
能够允许重载的操作符非常有限,只是一般的+ - 等等。
clr中更多的重载的可以参看
书中说到其实他调用了
op_addition method,
但使用 reflactor和il dasm都看不到那个方法。。。
查看的结果
private static void main(string[] args)
{
int a;
int b;
int c;
a = 0;
b = 0;
c = 0;
c = a + b;
return;
}.method private hidebysig static void main(string[] args) cil managed
{
.entrypoint
// code size 12 (0xc)
.maxstack 2
.locals init ([0] int32 a,
[1] int32 b,
[2] int32 c)
il_0000: nop
il_0001: ldc.i4.0
il_0002: stloc.0
il_0003: ldc.i4.0
il_0004: stloc.1
il_0005: ldc.i4.0
il_0006: stloc.2
il_0007: ldloc.0
il_0008: ldloc.1
il_0009: add
il_000a: stloc.2
il_000b: ret
} // end of method program::main
我的个人观点:
操作符重载通常会造成歧义而不被推崇,除非是为了性能特殊地方使用。如某些基础数据类型中需要重写比较==和!=等等。
1 c#的扩展方法
从一个简单的例子开始
namespace system {
public static class class4 {
public static string with(this string content, params string[] strs) {
return string.format(content, strs);
}
}
}
.................主程序..............
using system;
namespace clrlearn {
class program {
static void main(string[] args) {
display("hi {0}and{1} !".with(" ladys ", " gentleman !"));
console.read();
}
static void display(object ob) {
console.writeline(datetime.now.tostring() + " " + ob.tostring());
}
}
}
值得注意的是 在program 的.cs文件里 并没有 引用 扩展方法的命名空间,
因为他的命名空间就是system...这个又好又坏,对于多人来说,按理说命名空间不要取默认的
一些说明:
1 c#只支持扩展方法,不支持扩展属性,扩展事件。。。等
2 方法名无限制,第一个参数必须带this
2为集合做扩展方法
public static void showitems(this ienumerable collection) {
foreach (var item in collection)
console.writeline(item);
}
static void main(string[] args) {
string statment= "hi {0} and {1}".with("ladys", "gentleman");
display(statment);
statment.showitems();
console.read();
}
3更多细节
在你使用this参数扩展了方法之后,该程序集会在编译的时候会在对应静态类上加上类似以下的东西。以便于调用的时候方便找到。
[attributeusage(attributetargets.method | attributetargets.class | attributetargets.
assembly)]
public sealed class extensionattribute : attribute {
}
而他其实在运行时是需要引用system.core.dll的。