読者です 読者をやめる 読者になる 読者になる

vb.net から c# に書き換えたお話.

hoehoe2 の話の続きで,とくに実装の方の話.

変換

ShapeDevelop に Tween.sln を読み込ませて,Tween プロジェクトを右クリックして Convert -> From VB.Net to C# とやってハイおしまい.と行ければよかったのだけどもそんなことはなく色いろあるので対応.

  1. まず異常終了するので,変換後のプロジェクトが入るフォルダを見てどのファイルまで変換して落ちたのかを特定.
  2. ファイルを特定できたら上から半分を  #if 0 #endif で隠して変換.
  3. それでも落ちたら下半分を if-endif で隠して変換.と二分探索を繰り返してメソッド一つまで特定.
  4. ちなみに,落ちてたのはAppendSettingDialog.vb の 2466行目あたり,Twitter.vb の 3083 行目あたりと3143行目あたりなので目印つけてコメントアウトし変換.
  5. 異常終了はしないものの,変換できないファイルがあると,変換のログ出力に言われるのでそれの対応をする.
  6. ちなみに変換に失敗してたのは,Dynamic.vb の nullable, ImageDictionary.vb の Dictionary の From を使った初期化,Tween.vb での AndAlso/OrElse と LINQ の文末の判定処理,Spliter2.Panel2.Resize ハンドラ,Twitter.vb の Select Case の Is <> と言った箇所.続きは diff で
  7. これでめでたく C# になったので,Visual Studio で Tween.sln を開いてビルド出来る状態まで修正することになる.

ビルド出来るまで

めでたく逐語訳で VB.Net から C# になったものの,いくつか超えられない文法上の壁や ShapeDevelop 側のバグでビルド出来無いので対応する.変換器の異常終了への対応でコメントアウトした部分はここで手作業で変換した.

ディレクティブ

VB.Net だと #If 0/#End If#If UA = "True" Then/#End If とできるが C# だとできないので #if NOTDEFINED/#endif などで置き換える.

try-catch-finaly などで中括弧{}が挿入される箇所とディレクティブが重なるとうまく処理できないようで

Try
  [module] = assembly.DefineDynamicModule("Module")
Finally
#If ENABLE_LINQ_PARTIAL_TRUST Then
  PermissionSet.RevertAssert()
#End If
End Try

try{
  module = assembly.DefineDynbamicModule("Module");
#if ENABLE_LINQ_PARTIAL_TRUST 
}finally{
  PermissionSet.RevertAssert();
#endif
}

となってしまっていたので機械的に書き換えた.

Property

VB だと Property であっても,メソッドのように好き勝手に引数を付けて呼び出せるけども,C# だとそうは行かないので関数で置き換える.VB で引数のある無しで多重定義された Property もあるので適宜置き換えていく.SharpDevelop での変換でProperty の引数がなかったことにされているので,1つずつ確認していった.

配列

ary[idx] となるべきところがary(idx)と変換されないままになることがあるので,機械的に置き換えた.

LINQ

LINQ (query 式) の改行がなかったことにされたり,let や select がなかったことにされて意味が分からないことになってるので,vb 側のコードを見ながら置き換えた.

関数 ( ref/out )

C# だと関数の ref 引数にはデフォルトパラメータを与えられなかったり,property や null を ref に渡せなかったり,out にはちゃんと代入しないといけなかったり,値を返さないパスがあっちゃダメだったりと細かいところで VB が緩いので1つずつ潰していった.

Generic

変換器の制約で, Sub foo(Of int)foo<int>() となるべきところが ただの foo() にされていたので対応箇所を1つずつ確認していった.

その他

その他細かい文法上の違いで,interface に delegate を定義してたり(IHttpConnection.vb),内部クラスのそのまた内部 Interface を自分自身に適用させてたり(WebBrowserController.vb) と言った箇所を適当に変形させて,変換器が解釈をミスしまくってた Win32Api.vb をがっつり書き換えていった.

ビルド

このあたりで一応ビルドできるのでしてみると一応動作するものの関連 tweet をたどれなかったり,thumbnail が出なかったりで,時々例外を吐くのでそれを手がかりに変換ミスを探り,

VB だと field と同じように Property に初期値を 与えてるものがあって,変換の時にその処理が抜けていたのが原因だったので,プロパティの初期化をコンストラクタに追加して修正.

これでひとまず文法上はC# になったのでより C# らしく,Microsoft.VisualBasic.dll への参照を順次削除する作業にかかった.

より C# らしく

DateAndTime,Strings,vbConstants

DateAndTime は DateTime に,Strings の各関数は同等の機能に,vbConstants の CrLf は "\r\n" にと機械的に置き換えた.

イベント

まず VB.net ではコントロールのイベント処理(例:ボタンのクリックイベント)がこんな感じになる.

  1. Form1.Designer.vb で Private WithEvents button1 as Button としてボタンを宣言
  2. Form1.Designer.vb の InitializeComponent() で button1 = new Button()
  3. Form1.vb で Private Sub button1_click() Handles button1.click として処理内容を書く

これに対して,C# だと

  1. Form1.Designer.cs で private Button button1; と宣言
  2. Form1.Designer.cs  の InitializeComponent() で button1 = new Button(); して button1.click += button1_click;
  3. Form1.cs で private void button1_click() で処理内容を書く

となる.通常のフォームのコントロールであれば InitializeComponent() で一回作ればフォームが死ぬまで作り変えられることはないのでイベントハンドラの追加も一回あればよく,C# で明示的にハンドラを追加していることに特に問題はない.逆に言うと,C# では new して作ったオブジェクトには明示的にハンドラを追加してやらないと問題がでるということである.これを解決するために ShapeDevelop が取った方法が,プロパティの set{} に隠蔽するという方法で,汎用性を考えると悪くない方法なのだが,Control には周りくどい上デザイナでうまく扱えないので InitializeComponent() で追加するように全て置き換えた.

この変換に関して VB で WithEvents が付いていて同時に初期化もされるフィールドを,C# で フィールドとプロパティにしたときにフィールドの初期化はするものの,プロパティの set {} を経由しないのでイベントハンドラが割当てられないという SharpDevelop の変換処理の不具合があって,タイマー処理がされなくなっていたのでタイマーの new 直後にイベントハンドラの追加コードを追加して修正した.

Exit For

VB だと割りと自由にブロックを抜け出せる.

例えば,

For Each Ele In Elements
  Select Case Ele.attr
     Case AttrA
       foo()
       Exit For
  End Select
Next
bar()

とすれば Ele.attr が AttrA のとき bar() に制御を移せるが,C# でこれを実現する文法上の方法はないので

bool flg = false;
foreach( var Ele in Elements ){
  switch(Ele.attr){
    case AttrA: 
      foo(); 
      flg = true;
      break;
  }
  if ( flg ) { break; }
}
bar();

と一手間掛ける必要がある.

幸いにも,変換処理でこの手の Exit にはすべて TODO: が付けられているので 1つずつ適切に変換することが出来た.

Static Local

VB にだけある機能として Sub で Static な変数を定義できる.Sub が呼び出されるても値が初期化されず残る C や C++ の関数の static 変数と同じ機能がある.ところが,C# の文法にはないので,クラスのフィールドと Microsoft.VisualBasic.CompilerServices.StaticLocalInitFlag を使うように変換される.ところが,実際にはフィールドにするくらいで十分(だろう)と判断してさっくりと削除した.

My

VB のアプリの最大の特徴とも言えるのがこの My で,この My の下に Settings や Resources があり,その他にすべてのフォームのインスタンスやApplication,キーボード,オーディオとあらゆるものが詰め込まれていてほとんどどこからでも呼び出せる.C# だと Settings や Resources は普通 Properties 以下に配置され,Application ではなく Assembly.GetExecutingAssembly を経由する.

名前空間は置き換えれば済んだし,その他も同等の機能を実装して対応した.

My.Forms を経由した別フォームへのアクセスも実際には TweenMain (form) が Owner として設定されているのでそれを経由することで対応した.

最後まで残ったのは MyApplication である.

MyApplication

MyApplication とは何かというのは,そもそもアプリがどう起動するかという話で,面倒なので http://www.atmarkit.co.jp/fdotnet/dotnettips/631vbappfx/vbappfx.html でも読んでもらうとして,結局は Main にベタに書けば済むことなので applicationEvents.vb から Program.cs に移してめでたく終了.

 

My や MyApplication の理解があやふやで, 実際 Culture 周りが残ったままなので ,これじゃマズイのかもしれないけどもひとまずの記録として.