打從筆者開始接觸.NET平台的時候就是使用C#語言,在這之前擅長的程式語言工具是Delphi與VB5/6,算一算8-9年沒寫VB了。不黯VB的我,最近所接手的維護案子許多都是VB.NET開發的,由於部門大部分為C#的人力,而為了後續維護的方便,當時筆者就有一個構想,一定要將它整個轉換成C#語言
打從筆者開始接觸.NET平台的時候就是使用C#語言,在這之前擅長的程式語言工具是Delphi與VB5/6,算一算8-9年沒寫VB了。不黯VB的我,最近所接手的維護案子許多都是VB.NET開發的,由於部門大部分為C#的人力,而為了後續維護的方便,當時筆者就有一個構想,一定要將它整個轉換成C#語言。不過當然早在好幾年前仿間其實早就有許多VB.NET to C#或是C# to VB.NET等工具,有許多是線上的,如:
1.dotnetspider.com
http://www.dotnetspider.com/convert/Csharp-To-Vb.aspx
2.DeveloperFusion
http://www.developerfusion.com/tools/convert/csharp-to-vb/
3.或是CodeConverter
http://codeconverter.sharpdevelop.net/SnippetConverter.aspx
它提供更多種語言的轉換,與Boo, Python, Ruby之間的轉換,與C#/VB.NET之間因此共八種:
- C# to VB.NET
- C# to Boo
- C# to Python
- C# to Ruby
- VB.NET to C#
- VB.NET to Boo
- VB.NET to Python
- VB.NET to Ruby
4.CodeTranslator (VB.NET & C#) 互轉工具
這是筆者最愛使用的線上轉換工具,因為筆者覺得它轉出來的正確率最高。
關於這一類的互轉工具,喵大、保哥、艾小克 都有介紹過了。
我查詢到的連結如下,有興趣的也可以參考一下 :)
VB.NET 與 C# 語言轉換器
線上 C# 與 VB.NET 互轉工具
轉換的工具中也有GUI介面板的,如Convert.NET,我想許多人應該都很熟悉,如下:
但筆者要的不是這個,因為這都只是單一個程式碼轉換的工具,而且各家實作的轉換器可能有些不同,不見得所有VB.NET的表示方法都轉的成功,且VB.NET轉C#在先天上有一點限制的,比如說大小寫,VB.NET是不分大小寫的,如果你有變數在使用的地方與宣告的地方大小寫有差異,在C#裡編譯時是會發生錯誤的。還有Event Handler的部分,在VB.NET是可以省略不寫或是使用Handler關鍵字。所以當然還是會需要人工的處理,不過筆者希望的是能夠整個Solution的轉換,因為在實務上往往就是如此。
筆者找到另外在GUI介面版中,如果是專案的轉換,還有一套比較專業的VBConversions 但是是要收費的。試用版有1100行程式碼的限制,主畫面如下圖:
可參考http://www.vbconversions.net
一些比較專業的軟體是要付費的,筆者想暫不討論付費的部分,有時因為實務上、成本的考量,我們必須自行吸收掉這一段,且筆者手上其實都有足夠的C#人力來Review 程式碼,且專案本身不大,只要工具可以解決大部分的轉換即可。也許有讀者會問那為什麼要轉?其實就因素上來說,維護的考量與公司政策考量上都有。
在實做轉換的過程當中,筆者也想到了以前曾經用過的以Reflector加上FileDisassembler元件來將整個DLL轉換為C#專案,它需要您的專案有編譯完成的DLL檔案,這是缺點之一,如下圖:
操作的方式只要選定你要Disassembly的DLL,接著點選[Tools –> File Disassembly] 即會出現如下圖步驟3的小視窗。
通常DLL會是Class Library專案,它比較強的地方就是連專案檔都幫你產生出來。可是還是有許多缺點。
不過Reflector所Disassembler出來的程式碼是不見得可以再放回編譯器執行的,它是從中介語言IL再轉換過來。比如說在IL裡不會知道你的變數命名是什麼?它會自動以 "語言$變數型態$個數" 來命名。且後來Reflector也要收費了,所以這個方式就不考慮了。
因此筆者最後決定使用的還是SharpDeveloper所提供的轉換功能 (SharpDeveloper也是一套筆者非常喜歡使用的一套免費的.NET IDE工具,大約從0.9 Beta時筆者就開始用了,現在最新版為4.0),如果您使用SharpDeveloper開啟一個VB.NET的Web專案(.NET 2.0的),在選單的 [Project] –> [Convert] –> [From VB.NET to C#] ,如下圖:
轉換完成時會產生一個錯誤報告,它會先告訴你VB.NET有哪些關鍵字是C#不支援的。
如上圖中,在Account_Data.aspx.designer.vb 等...中C#不支援的語法為 "Option Strict Off" 與 "Option Explicit On",這其實我們可以不加以理會,因為再轉換完成的Account_Data.aspx.designer.cs 檔案中也沒有不會將這個不支援的關鍵字產生出來 。
註:如果您是Web應用程式專案,每個ASPX可能都會有一個designer檔案
下圖為原始的
而SharpDeveloper轉換出來的程式碼其實很漂亮,暫撇開語言上的限制以及無法轉換的部分,SharpDeveloper轉出來的專案不會輸給專業的轉換器,而重要的是它是免費的。如下筆者節錄一些轉換出來的UI與designer程式碼 (因為部分牽涉商業機密我只節錄出部分比較無關緊要的部分):
轉換後的 Account_Data.aspx.designer.cs 程式碼:
1: using Microsoft.VisualBasic;
2: using System;
3: using System.Collections;
4: using System.Configuration;
5: using System.Data;
6: using System.Drawing;
7: using System.Web;
8: using System.Web.UI;
9: using System.Web.UI.HtmlControls;
10: using System.Web.UI.WebControls;
11: //------------------------------------------------------------------------------
12: // <auto-generated>
13: // 這段程式碼是由工具產生的。
14: // 執行階段版本:2.0.50727.3607
15: //
16: // 對這個檔案所做的變更可能會造成錯誤的行為,而且如果重新產生程式碼,
17: // 變更將會遺失。
18: // </auto-generated>
19: //------------------------------------------------------------------------------
20:
21: // ERROR: Not supported in C#: OptionDeclaration
22: namespace ConfidentialByCompany
23: {
24: ///<summary>
25: ///CDC_Account_Data 類別。
26: ///</summary>
27: ///<remarks>
28: ///自動產生的類別。
29: ///</remarks>
30: public partial class Account_Data
31: {
32:
33: ///<summary>
34: ///lnkCssStyle 控制項。
35: ///</summary>
36: ///<remarks>
37: ///自動產生的欄位。
38: ///若要修改,請將欄位宣告從設計工具檔案移到程式碼後置檔案。
39: ///</remarks>
40:
41: protected global::System.Web.UI.HtmlControls.HtmlGenericControl lnkCssStyle;
42: ///<summary>
43: ///form1 控制項。
44: ///</summary>
45: ///<remarks>
46: ///自動產生的欄位。
47: ///若要修改,請將欄位宣告從設計工具檔案移到程式碼後置檔案。
48: ///</remarks>
49:
50: ///略...........................................................................................
51:
52: protected global::System.Web.UI.WebControls.Image Image7;
53: ///<summary>
54: ///imgbAdd 控制項。
55: ///</summary>
56: ///<remarks>
57: ///自動產生的欄位。
58: ///若要修改,請將欄位宣告從設計工具檔案移到程式碼後置檔案。
59: ///</remarks>
60: private global::System.Web.UI.WebControls.ImageButton withEventsField_imgbAdd;
61: protected global::System.Web.UI.WebControls.ImageButton imgbAdd {
62: get { return withEventsField_imgbAdd; }
63: set {
64: if (withEventsField_imgbAdd != null) {
65: withEventsField_imgbAdd.Click -= imgbAdd_Click;
66: }
67: withEventsField_imgbAdd = value;
68: if (withEventsField_imgbAdd != null) {
69: withEventsField_imgbAdd.Click += imgbAdd_Click;
70: }
71: }
72:
73: }
74: private global::System.Web.UI.WebControls.CheckBox withEventsField_chkORG_Admin;
75: protected global::System.Web.UI.WebControls.CheckBox chkORG_Admin {
76: get { return withEventsField_chkORG_Admin; }
77: set {
78: if (withEventsField_chkORG_Admin != null) {
79: withEventsField_chkORG_Admin.CheckedChanged -= chkORG_Admin_CheckedChanged;
80: }
81: withEventsField_chkORG_Admin = value;
82: if (withEventsField_chkORG_Admin != null) {
83: withEventsField_chkORG_Admin.CheckedChanged += chkORG_Admin_CheckedChanged;
84: }
85: }
86:
87: }
88: ///<summary>
89: ///chkORG_User 控制項。
90: ///</summary>
91: ///<remarks>
92: ///自動產生的欄位。
93: ///若要修改,請將欄位宣告從設計工具檔案移到程式碼後置檔案。
94: ///</remarks>
95: }
96: }
而轉換完成的Account_Data.aspx.cs 的程式碼也很漂亮,這些都是可以直接使用C#編譯器 csc.exe 再進行編譯的,如下:
1: using Microsoft.VisualBasic;
2: using System;
3: using System.Collections;
4: using System.Configuration;
5: using System.Data;
6: using System.Drawing;
7: using System.Web;
8: using System.Web.UI;
9: using System.Web.UI.HtmlControls;
10: using System.Web.UI.WebControls;
11: using System.Data.SqlClient;
12:
13: namespace ConfidentialByCompany
14: {
15: public partial class Account_Data : BaseForm
16: {
17: SqlDataReader dr;
18: protected void Page_Load(object sender, System.EventArgs e)
19: {
20: if (!IsPostBack) {
21: BindData();
22: //ButtonControl("")
23: base.AddImageBtnAttributes(ref this);
24: }
25: }
26: /// 略..................(注意:此段刪掉關鍵部分,只演示轉換功能並不能提供您做他用途).....................
27:
28: private void ButtonControl(string ControlMode)
29: {
30: if ((ControlMode.Equals("EDIT"))) {
31: imgbAdd.Enabled = false;
32: imgbCancel.Enabled = true;
33: imgbDelete.Enabled = true;
34: imgbEdit.Enabled = true;
35: } else {
36: imgbAdd.Enabled = true;
37: imgbCancel.Enabled = false;
38: imgbDelete.Enabled = false;
39: imgbEdit.Enabled = false;
40: }
41:
42: }
43: private void imgbAdd_Click(System.Object sender, System.Web.UI.ImageClickEventArgs e)
44: {
45: if (!CheckInput("I")) {
46: return;
47: }
48:
49: bool ExecuteResult = false;
50:
51: try {
52: /// 略..................(注意:此段刪掉關鍵部分,只演示轉換功能並不能提供您做他用途).....................
53:
54: //帳號重複
55: if (((dt != null) && dt.Rows.Count > 0 && dt.Rows[0]["CNT"] > 0)) {
56: base.ErrMessageBox("帳號已經存在!!");
57: return;
58: }
59:
60: ExecuteResult = UserAccount_Edit(txt_LoginID.Text, txt_UserName.Text, txt_Password.Text, txt_EMail.Text, chk_Enable.Checked, System.DateTime.Now(), ddl_OID.SelectedValue, chkORG_Admin.Checked, chkORG_User.Checked, Session["LoginID"],
61: System.DateTime.Now(), Session["LoginID"], System.DateTime.Now(), "I");
62:
63: if (ExecuteResult) {
64: base.ErrMessageBox("新增成功!!");
65: } else {
66: base.ErrMessageBox("新增失敗!!");
67: }
68:
69: } catch (Exception ex) {
70: base.ErrMessageBox(ex.Message);
71: } finally {
72: DataGrid1.SelectedIndex = -1;
73: base.setButtonStatus(DataGrid1.SelectedIndex, ref this);
74: ControlClear(ref this);
75: BindData();
76: }
77: }
78: protected void chkORG_Admin_CheckedChanged(System.Object sender, System.EventArgs e)
79: {
80: chkORG_User.Checked = !chkORG_Admin.Checked;
81: if (chkORG_Admin.Checked) {
82: chkORG_User.Enabled = false;
83: }
84: }
85: }
86: }
注意:以上的程式碼只是筆者展示SharpDeveloper轉換的結果,節錄出來的只有部分的程式碼,您可能無法拿此程式做騎它的用途
如上程式碼,筆者節錄出與Account_Data.aspx.designer.cs 共通的部分,包含了 一個ImageButton的新增事件與CheckBox的CheckedChanged事件。各位如果要注意看的話會發現SharpDevelper在designer檔案加入了幾段怪怪的程式碼、如了個私有變數 withEventsField_imgbAdd。其實是 SharpDevelper 很雞婆的將UI控制項的宣告改以屬性的方式 (主要在只要屬於UI控制項的事件),在取得該屬性的時候自動繫結該事件。這是比較需要注意的地方。主要原因就是如果您在VB裡使用 Handler 關鍵字,如下:
(問題一)
Private Sub imgbAdd_Click(ByVal sender As System.Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles imgbAdd.Click
……略
End Sub
(問題二)
這裡筆者會建議您自己重新繫結事件,因為當您再換回Visual Studio 來編輯的時候,放在designer內的程式碼隨時會因為您在IDE下更動UI控制項時被重新產生而清空,到時您就會發現網頁所有事件都不會動.. 。
還有一些撰寫VB的習慣可能在Function內傳入引數的時候不宣告任何型態,只有一個ByVal關鍵字而已,如下VB.NET程式:
1: Public Sub ErrMessageBox(ByVal strErrMessage)
2: ///..略.........................
3: End Sub
這樣的程式碼經過SharpDeveloper 轉換後會只剩下一個strErrMessage參數,在C#裡是不允許的,如下:
1: public void ErrMessageBox(strErrMessage)
2: {
3:
4: ///..略.....................................
5: }
(問題三)
再來是您的VB.NET內如果有 Optional 關鍵字,因為VB是允許這樣參數可有可無的寫法,在這裡的轉換是會有問題的,如下VB.NET程式:
Public Function ExecSQLScaler(ByVal strSPName As String, ByVal ParamValue() As SqlParameter, Optional ByRef ConnectionString As String = "") As Object
這裡筆者會建議就使用 overload 機制另外撰寫一個沒有ConnectionString 參數的方法,如下:
1: public object ExecSQLScaler(string strSPName, SqlParameter[] ParamValue)
2: {
3: return ExecSQLScaler(strSPName, ParamValue, "");
4: }
5: /// <summary>
6: /// 傳回SP 的ExecuteScaler() 的方法
7: /// Add by Gelis at 2010/12/07.
8: /// </summary>
9: /// <param name="strSPName"></param>
10: /// <param name="ParamValue"></param>
11: /// <param name="ConnectionString"></param>
12: /// <returns></returns>
13: /// <remarks></remarks>
14: public object ExecSQLScaler(string strSPName, SqlParameter[] ParamValue, string ConnectionString)
15: {
16: IDbTransaction MyTrans = null;
17: SqlCommand objCmd = new SqlCommand();
18: string strSQL = null;
19:
20: try {
21: if (string.IsNullOrEmpty(ConnectionString)) {
22: ConnectionString = "ConnectionString";
23: }
24: objConnection = new SqlConnection(ConfigurationSettings.AppSettings[ConnectionString]);
25: if (objConnection.State == ConnectionState.Closed)
26: objConnection.Open();
27: MyTrans = objConnection.BeginTransaction();
28: objCmd.Connection = objConnection;
29: objCmd.Transaction = MyTrans;
30: objCmd.CommandText = strSPName;
31: objCmd.CommandType = CommandType.StoredProcedure;
32: int i = 0;
33: for (i = 0; i <= Information.UBound(ParamValue); i++) {
34: objCmd.Parameters.Add(ParamValue[i]);
35: }
36: object result = objCmd.ExecuteScalar();
37: MyTrans.Commit();
38: return result;
39:
40: } catch (SqlException ex) {
41: MyTrans.Rollback();
42: ErrMessageBox(ex.Message);
43: return false;
44: } catch (Exception ex) {
45: MyTrans.Rollback();
46: ErrMessageBox(ex.Message);
47: return false;
48: } finally {
49: if (objCmd.Connection.State == ConnectionState.Open)
50: objCmd.Connection.Close();
51: objCmd.Dispose();
52: }
53: }
如此之下,傳換後C#程式碼,再不修改該方法的呼叫端的情況下程式也可以順利執行,因為同樣ConnectionString 你要傳都沒有影響,主方法內部會判斷是否有傳入值。
(問題四)
您的程式你若有未宣告型別的變數,可能會有問題,如下:
Dim strSQL = "select Cname,Memo,RG from [TableName] where ID = " & key
在SharpDeveloper會轉成以dynamic來宣告:
dynamic strSQL = "select Cname,Memo,RG from [TableName] where ID = " + key;
(問題五)
還有也是VB.NET可以省略的寫法,這在C#裡都是需要注意的,其它還有許多,如最簡單的不寫ToString(),在轉換成C#程式時都會有問題,這裡筆者並不是歧視VB.NET語法,個人覺得這有時後是寫程式的習慣問題,因為筆者曾遇過一些從VB.NET轉換到C#的工程師,最難以習慣的就是C#在任何地方一定要告知其型態,需要轉型就必須轉型,以及需要字串就必須ToString()等,因此筆者通常會要求即使您是使用VB.NET來撰寫程式,我還是會要求需要轉型就轉型,需要ToString()就要ToString(),這我在VB.NET的Coding Standard 中就會加以要求。
如下面程式,DropDownList在FindByValue 時底下的VB.NET可以順利執行:
1: ddlMonth.Items.FindByValue(DateTime.Now.Month).Selected = True
2: ddlYear.Items.FindByValue(DateTime.Now.Year).Selected = True
但是轉換成C#是無法順利執行的,熟悉C#的朋友應該馬上看出問題了!還有DataRow也是,您可能常看到如下程式:
1: ddl_OID.Items.Add(new ListItem(dr["O_NAME"], dr["OID"]));
還有DateTime的部分,VB.NET是可以這樣寫的
1: Date.Now()
但是轉換到C#是無法編譯過的!總之這一類型的都要注意,當然還有許多,不過都是類似的問題,筆者就不再熬敘。
(問題六)
由於SharpDeveloper並不會幫您將ASPX檔案內的 Codebehind 或 CodeFile 從.vb 改為 .cs ,所以您要自行修改。這就只有專業的轉換器才會幫您轉換,這您就非常需要特別注意。不想花錢… 就自己轉.. 這.. 賣那個傳業級工具的不要打我.. Orz。
(問題七)
還是事件的問題,因為SharpDeveloper還是如同單一檔案方式進行轉換(只是以批次的方式),無法像專業級的轉換器會根據 (Project/Solution) 的類型進行一些其他的處理。如同前面 Handler 關鍵字問題,SharpDeveloper 會無法轉換出Page_Load事件的繫結,它轉換出的可能只剩下如下:
1: #region " Web Form 設計工具產生的程式碼 "
2:
3: //此為 Web Form 設計工具所需的呼叫。
4: [System.Diagnostics.DebuggerStepThrough()]
5:
6: private void InitializeComponent()
7: {
8: }
9:
10: //注意: 下列預留位置宣告是 Web Form 設計工具需要的項目。
11: //請勿刪除或移動它。
12:
13: private System.Object designerPlaceholderDeclaration;
14: private void Page_Init(System.Object sender, System.EventArgs e)
15: {
16: //CODEGEN: 此為 Web Form 設計工具所需的方法呼叫
17: //請勿使用程式碼編輯器進行修改。
18: InitializeComponent();
19: }
20:
21: #endregion
22:
23: ...略
24: public Account_Data()
25: {
26: Load += Page_Load;
27: }
各位應該看出問題了,這段程式對C#而言,有跟沒有是一樣的!因為根本不會跑!因為Page_Load事件永遠不會被觸發。它在Constructor裡的Load += Page_Load 是不正確的。應將這段 "Web Form 設計工具所需的呼叫" 部分,程式碼改為如下:
1: #region " Web Form 設計工具產生的程式碼 "
2:
3: //此為 Web Form 設計工具所需的呼叫。
4: [System.Diagnostics.DebuggerStepThrough()]
5: private void InitializeComponent()
6: {
7: Load += new EventHandler(Page_Load);
8: }
9: //注意: 下列預留位置宣告是 Web Form 設計工具需要的項目。
10: //請勿刪除或移動它。
11: private System.Object designerPlaceholderDeclaration;
12: private void Page_Init(object sender, EventArgs e)
13: {
14: //CODEGEN: 此為 Web Form 設計工具所需的方法呼叫
15: //請勿使用程式碼編輯器進行修改。
16: InitializeComponent();
17: }
18: #endregion
其它事件也必須自行繫結。
如上經驗分享。
也感謝各位朋場,若您有其他類似的經驗也歡迎您不吝指教。
謝謝看這裡讀者:D
簽名:
學習是一趟奇妙的旅程
這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。
軟體開發之路(FB 社團):https://www.facebook.com/groups/361804473860062/
Gelis 程式設計訓練營(粉絲團):https://www.facebook.com/gelis.dev.learning/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^