將VB.NET專案轉換成C#專案(實務篇1)

打從筆者開始接觸.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

image

2.DeveloperFusion

http://www.developerfusion.com/tools/convert/csharp-to-vb/

image

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

image

4.CodeTranslator (VB.NET & C#) 互轉工具

這是筆者最愛使用的線上轉換工具,因為筆者覺得它轉出來的正確率最高。

image

關於這一類的互轉工具,喵大、保哥、艾小克 都有介紹過了。

我查詢到的連結如下,有興趣的也可以參考一下 :)

C# 與 VB.NET程式碼互轉

VB.NET 與 C# 語言轉換器

線上 C# 與 VB.NET 互轉工具

 

轉換的工具中也有GUI介面板的,如Convert.NET,我想許多人應該都很熟悉,如下:

image

但筆者要的不是這個,因為這都只是單一個程式碼轉換的工具,而且各家實作的轉換器可能有些不同,不見得所有VB.NET的表示方法都轉的成功,且VB.NET轉C#在先天上有一點限制的,比如說大小寫,VB.NET是不分大小寫的,如果你有變數在使用的地方與宣告的地方大小寫有差異,在C#裡編譯時是會發生錯誤的。還有Event Handler的部分,在VB.NET是可以省略不寫或是使用Handler關鍵字。所以當然還是會需要人工的處理,不過筆者希望的是能夠整個Solution的轉換,因為在實務上往往就是如此。

筆者找到另外在GUI介面版中,如果是專案的轉換,還有一套比較專業的VBConversions 但是是要收費的。試用版有1100行程式碼的限制,主畫面如下圖:

image

可參考http://www.vbconversions.net

 

一些比較專業的軟體是要付費的,筆者想暫不討論付費的部分,有時因為實務上、成本的考量,我們必須自行吸收掉這一段,且筆者手上其實都有足夠的C#人力來Review 程式碼,且專案本身不大,只要工具可以解決大部分的轉換即可。也許有讀者會問那為什麼要轉?其實就因素上來說,維護的考量與公司政策考量上都有。

在實做轉換的過程當中,筆者也想到了以前曾經用過的以Reflector加上FileDisassembler元件來將整個DLL轉換為C#專案,它需要您的專案有編譯完成的DLL檔案,這是缺點之一,如下圖:

image

操作的方式只要選定你要Disassembly的DLL,接著點選[Tools –> File Disassembly] 即會出現如下圖步驟3的小視窗。

image

通常DLL會是Class Library專案,它比較強的地方就是連專案檔都幫你產生出來。可是還是有許多缺點。

image

不過Reflector所Disassembler出來的程式碼是不見得可以再放回編譯器執行的,它是從中介語言IL再轉換過來。比如說在IL裡不會知道你的變數命名是什麼?它會自動以 "語言$變數型態$個數" 來命名。且後來Reflector也要收費了,所以這個方式就不考慮了。

image

因此筆者最後決定使用的還是SharpDeveloper所提供的轉換功能 (SharpDeveloper也是一套筆者非常喜歡使用的一套免費的.NET IDE工具,大約從0.9 Beta時筆者就開始用了,現在最新版為4.0),如果您使用SharpDeveloper開啟一個VB.NET的Web專案(.NET 2.0的),在選單的 [Project] –> [Convert] –> [From VB.NET to C#] ,如下圖:

image

轉換完成時會產生一個錯誤報告,它會先告訴你VB.NET有哪些關鍵字是C#不支援的。

image

如上圖中,在Account_Data.aspx.designer.vb 等...中C#不支援的語法為 "Option Strict Off" 與 "Option Explicit On",這其實我們可以不加以理會,因為再轉換完成的Account_Data.aspx.designer.cs 檔案中也沒有不會將這個不支援的關鍵字產生出來 。

註:如果您是Web應用程式專案,每個ASPX可能都會有一個designer檔案

下圖為原始的

image

而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。

image

 

(問題七)

還是事件的問題,因為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/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^