2008年9月6日 星期六

VirtualFunction

class Class1
{
public String Function() { return "Class1.Function"; }
public virtual String VirtualFunction() { return "Class1.VirtualFunction"; }
}

class Class2 : Class1 // 冒號代表"繼承自"
{
public new String Function() { return "Class2.Function"; }
// 只要sub-class與base-class有相同名稱的成員 該成員必須註明new明白表示"取代"
public override String VirtualFunction() { return "Class2.VirtualFunction"; }
// 若要繼承base-class的virtual-function就必須註明override明白表示"凌越"而非"取代"
// 若VirtualFunction被註明new而非override會造成什麼後果
// 以及override與new的差別將在後面說明
}

public partial class Form1 : Form
{

Class1 O = new Class2();

public Form1()
{
InitializeComponent();

label1.Text += "Class1 O = new Class2();" + Environment.NewLine
+ Environment.NewLine;
label1.Text += "O.Function() calls " + O.Function() + Environment.NewLine;
label1.Text += "O.VirtualFunction() calls " + O.VirtualFunction()
+ Environment.NewLine;
}


}

我寫了以上這小段簡單程式執行結果如下:


是給初學者示範virtual-function與一般function的差別的。若Class2.VirtualFunction被註明new而非override,則Class1.VirtualFunction沒有被Class2.VirtualFunction凌越,O.VirtualFunction() calls Class1.VirtualFunction。

Virtual-function的使用時機是當你要在run-time才決定要bind繼承體系中哪個class的function時就要把該function宣告成virtual。如以上範例,Class2是Class1的子類別,所以可以assign一個Class2的object給Class1 O,一個object若是一個Class2也就是一個Class1。當宣告Class1 O後,程式中每次由O.都是bind到Class1的function,除非virtual-function否則即使O = new Class2()也是bind到Class1的function,這是因為Class1 O在compile-time就決定,程式編譯O.也是在load&link-time就把function的entry-address寫死在code-segment。如果要在run-time看哪個sub-class或base-class本身的object被assign到O才bind到O所屬衍生的class的function呢?Virtual-function的做法是不直接呼叫base-class的function的address而改以呼叫放在object中virtual-function的address,也就是說在object裡增加一個欄位記載function的address,相當於宣告定義:

Class1
{
public delegate String FunctionDelegate();
// delegate是指向函數的參照型別
// 此行宣告FunctionDelegate是傳回值型別為String及參引數型別為()的函式參照型別
public FunctionDelegate VirtualFunction; // compiler說非static成員不能在此初始化
public Class1() { VirtualFunction = Function; }
// compiler說非static成員只能在函式定義中assign
public String Function() { return "Class1.VirtualFunction"; }
}

Class2 : Class1
{
public Class2() { VirtualFunction = Function; }
public new String Function() { return "Class2.VirtualFunction"; }
}

如此,哪個object被assign到O,執行O.VirtualFunction時自然就呼叫到它所屬的衍生終端類別的函式。在最前面的原始程式碼中,Function是常數,VirtualFunction是變數,都可以assign給delegate。在最前面的原始程式碼中,若Class2.VirtualFunction被註明new而非override,則在這裡相當於定義public Class2() { VirtualFunction = Class1.Function; }。

可是這樣會有什麼問題?當大量使用virtual-function時,會大量增加虛擬函數參照的欄位徒增object的size,當大量objects生成時,占用記憶體的空間更是呈倍數成長。有沒有其它辦法?如果在run-time才travel類別樹找到要bind的function呢?這個比較耗費時間的動作原先是在compile-time就做好的,在軟件製作時已經把程式寫死。如果在run-time才由object查詢其所屬class,再由class-tree的leaf開始往上找名稱符合的函式,這樣就以時間換取空間,以較多的程式執行減少記憶體空間的占用。將程式改寫如下:

class ClassA
{


public String VirtualFunction(String name)
{
try { return (String) this.GetType().GetMethod(name).Invoke(this, null); }
catch(NullReferenceException x) { return "null"; }
}

public String Function() { return "ClassA.Function"; }

}

class ClassB : ClassA
{
public new String Function() { return "ClassB.Function"; }
}

public partial class Form1 : Form
{

ClassA A = new ClassA(), B = new ClassB();

public Form1()
{
InitializeComponent();

label1.Text += "ClassA A = new ClassA(), B = new ClassB();"
+ Environment.NewLine + Environment.NewLine;
label1.Text += "A.Function() calls " + A.VirtualFunction("Function")
+ Environment.NewLine;
label1.Text += "B.Function() calls " + B.VirtualFunction("Function")
+ Environment.NewLine;
}

}

執行結果如下:

這啟示compiler可以把.Function()編譯成.VirtualFunction("Function")。

當衍生終端的class沒有定義符合名稱的function則要往base-classes去找,如果class繼承階層數太多會徒增要bind的function的提取時間,可以折衷在衍生終端的class宣告一個static-delegate記載virtual-function,執行時由object查詢其所屬class,再從class取得並呼叫這個delegate即可。由於宣告為static,欄位只存在class單元而非每一個object,所以並不耗費記憶空間。如此,程式可改寫如下:

class ClassA
{
public delegate String FunctionDelegate();
public static FunctionDelegate VirtualFunction1;
public ClassA() { VirtualFunction1 = Function; }

public String VirtualFunction(String name)
{

return ((FunctionDelegate) this.GetType()
.GetField(name).GetValue(this)).Invoke();
}

public String Function() { return "ClassA.Function"; }
}

class ClassB : ClassA
{
public static new FunctionDelegate VirtualFunction1;
public ClassB() { VirtualFunction1 = Function; }
}

class ClassC : ClassB
{
public static new FunctionDelegate VirtualFunction1;
public ClassC() { VirtualFunction1 = Function; }
}

class ClassD : ClassC
{
public static new FunctionDelegate VirtualFunction1;
public ClassD() { VirtualFunction1 = Function; }
public new String Function() { return "ClassD.Function"; }
}

class ClassE : ClassD
{
public static new FunctionDelegate VirtualFunction1;
public ClassE() { VirtualFunction1 = Function; }
}

使用時則是:


ClassA A = new ClassA();
ClassA B = new ClassB();
ClassA C = new ClassC();
ClassA D = new ClassD();
ClassA E = new ClassE();

A.VirtualFunction("VirtualFunction1");
B.VirtualFunction("VirtualFunction1");
C.VirtualFunction("VirtualFunction1");
D.VirtualFunction("VirtualFunction1");
E.VirtualFunction("VirtualFunction1");

結果:

A. calls ClassA.Function
B. calls ClassA.Function
C. calls ClassA.Function
D. calls ClassD.Function
E. calls ClassD.Function


倘若還要再減少耗費的時間,就只能從硬體著手,針對object-binding重新設計處理器,使用Sense G-machine。