建構函式的那些事
如果你不為類定義一個建構函式,他將在編譯時間候自動產生
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<t>(this ienumerable<t> 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<char>();
console.read();
}
3更多細節
在你使用this參數擴充了方法之後,該程式集會在編譯的時候會在對應靜態類上加上類似以下的東西。以便於調用的時候方便找到。
[attributeusage(attributetargets.method | attributetargets.class | attributetargets.
assembly)]
public sealed class extensionattribute : attribute {
}
而他其實在運行時是需要引用system.core.dll的。