ラベル C# vs VB の投稿を表示しています。 すべての投稿を表示
ラベル C# vs VB の投稿を表示しています。 すべての投稿を表示

2014年7月13日

オーバーロードの比較

比較といっても大きな違いは 1 つだけ。
C# は同じ型の引数でも、値渡しと参照渡し(ref または out)で別々のメソッドとして定義できるが、VB は不可。

具体的には、C# で次はコンパイル可。

public class TestOverloadCs
{
    public static void Action(int value)
    {
        Console.WriteLine("Action(int)");
    }
    public static void Action(ref int value)
    {
        Console.WriteLine("Action(ref int)");
    }
}

VB で次はコンパイル不可。

Public Class TestOverloadVb
    Public Shared Sub Action(ByVal value As Integer) 'コンパイルエラー
        Console.WriteLine("Action(int)")
    End Sub

    Public Shared Sub Action(ByRef value As Integer)
        Console.WriteLine("Action(ref int)")
    End Sub
End Class

VB は呼び出し側で ref とか out とかのキーワードを付けないから、上記のようなオーバーロードは禁止されているのだと思われる。

また、上記の C# のオーバーロードは、VB から普通に呼べないので注意。
VB_OverloadMethodCallError
リフレクションを使うとたぶん呼べる。

VB で使用するライブラリを C# で書く場合、上記のようなオーバーロードは避けるのが無難。
C# と VB のちゃんぽんはオススメしないが・・・

参考URL

2014年4月8日

VB の Module ≒ C# の static class

Java/C# ユーザーが初めて VB.NET に触れた時、「Module(モジュール)って何?」という疑問にぶち当たる確率は高いと思う。
簡単に言ってしまえば、静的メンバのみのクラス(C# の static class)で、呼び出し側でクラス名(モジュール名)を省略できる特徴がある。
文字だと分かりにくいのでコードで

Public Module ModuleSample
    Sub New()
        ' コンストラクタも定義できる(静的コンストラクタに該当)
        Console.WriteLine("ModuleSample.New")
    End Sub

    Public Sub TestCall()
        ' Shared が無いけど、静的メソッド(共有メソッド)
        Console.WriteLine("ModuleSample.TestCall")
    End Sub
End Module

Module EntryPoint
    ' モジュール/クラスの Main メソッドはプログラムのエントリポイント
    Sub Main()
        ' 呼び出し時
        TestCall()              'モジュール名を省略できる
        ModuleSample.TestCall() '省略しないことも可能
    End Sub
End Module

'[結果]
' ModuleSample.New
' ModuleSample.TestCall
' ModuleSample.TestCall

C# からは、static class として扱える。

static class EntryPoint
{
    static void Main()
    {
        ModuleSample.TestCall();
    }
}

//[結果]
// ModuleSample.New
// ModuleSample.TestCall

Module の実態は static class とほぼ同じなのだが、C# の static class を VB から呼ぶ場合は Module のようにクラス名を省略できない。その違いは、StandardModuleAttribute である。
この属性を C# の static class に付けてやると、VB から呼ぶ場合にクラス名を省略できる。
ただ、MSDN の説明に「独自に作成したコードから直接使用するためのものではありません。」「これは、コードから直接呼び出すためのものではありません。」とあり、ユーザーコードからは使って欲しくない模様・・・

using System;
using Microsoft.VisualBasic.CompilerServices;

[StandardModule]
public static class CShapModuleSample
{
    public static void TestCall()
    {
        Console.WriteLine("CShapModuleSample.TestCall");
    }
}

MSIL の比較

VB - Module .class public auto ansi sealed
C# - static class .class public abstract auto ansi sealed beforefieldinit
static class には、abstract/beforefieldinit が付いている。
static class の場合、beforefieldinit は静的コンストラクタがあると付かないが、Module の場合、コンストラクタの有無に関わらず付かない。
つまり、Module のフィールドは必ず最初のアクセス時に初期化される。(アクセスがなければ初期化されない)

拡張メソッド

VB の拡張メソッドは Module でしか作れない。

Imports System.Runtime.CompilerServices

Public Module Extensions
    <Extension()>
    Public Function IsNotNull(Of T)(ByVal source As T) As Boolean
        Return source IsNot Nothing
    End Function
End Module

' 呼び出し方法は 3 通り可能・・・
Dim val = ""
val.IsNotNull()
IsNotNull(val)
Extensions.IsNotNull(val)

まとめ

Module は多用しない方がいい。
クラス名(モジュール名)を省略することは、名前の衝突確率を上げることになり、あまりよくない。
プログラムのエントリポイント、拡張メソッド以外での利用は避けた方がいい。

どうしても使いたい場合は、アクセス修飾子を Friend にする、専用の名前空間にする、などの条件を設けるべき。
余談だが、拡張メソッドも名前の衝突確率が高いので、専用の名前空間を推奨。

VB は Module があるためか、静的クラスが無いので、代わりに下記のような疑似静的クラスを使ったりする。

' NotInheritable で継承禁止
' Private コンストラクタでインスタンス生成を禁止
Public NotInheritable Class StaticClass
    Private Sub New()
    End Sub

    ' ---- 以降、Shared メンバを定義 ----
End Class

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

2014年4月4日

プロパティの比較

項目 C#.NET VB.NET
静的プロパティ
読込専用プロパティ
書込専用プロパティ
get, set 毎のアクセス修飾子
引数付きプロパティ
インデクサ
自動プロパティ
自動プロパティの
get, set 毎のアクセス修飾子
自動プロパティの初期化
自動プロパティの
バッキングフィールド
<PropertyName>k__BackingField _PropertyName
※VB.NET の get, set 毎のアクセス修飾子は、Visual Studio 2008/2010 で可能、2003 では不可であることを確認。
MSDN を信じると Visual Studio 2005 から可能。

  • C#.NET

using System;
using System.Reflection;

class CSharpProperties
{
    // 静的プロパティ
    public static int SValue
    {
        get { return -1; }
        set { Console.WriteLine(value); }
    }

    // 読取専用プロパティ
    public int RValue { get { return 0; } }

    // 書込専用プロパティ
    public int WValue { set { Console.WriteLine(value); } }

    // get, set 毎のアクセス修飾子
    public int RWValue
    {
        get { return 0; } // 省略時はプロパティと同じ = public
        private set { Console.WriteLine(value); }
    }

    // 引数付きプロパティ -> 不可
    // public string ParamValue[int index] { get { return index.ToString(); } set { } }

    // インデクサ
    public string this[int index]
    {
        get { return index.ToString(); }
        set { Console.WriteLine("[{0}] = {1}", index, value); }
    }

    // 自動プロパティ
    public int AutoValue { get; set; }

    // 自動プロパティの get, set 毎のアクセス修飾子
    public int RAutoValue { get; private set; } // 外部から読取専用
    public int WAutoValue { private get; set; } // 外部から書込専用

    // 自動プロパティの初期化 -> 不可
    // public int IAutoValue { get; set; } = 1;

    // 自動プロパティのバッキングフィールドへのアクセス -> リフレクションで可能
    public void AccessToAutoValue()
    {
        var fi = this.GetType().GetField("<AutoValue>k__BackingField",
                                         BindingFlags.Instance | BindingFlags.NonPublic);
        Console.WriteLine(fi.GetValue(this));
    }
}

  • VB.NET

Imports System

Class VbProperties
    ' 静的プロパティ
    Public Shared Property SValue As Integer
        Get
            Return -1
        End Get
        Set(value As Integer)
            Console.WriteLine(value)
        End Set
    End Property

    ' 読取専用プロパティ
    Public ReadOnly Property RValue As Integer
        Get
            Return 0
        End Get
    End Property

    ' 書込専用プロパティ
    Public WriteOnly Property WValue As Integer
        Set(value As Integer)
            Console.WriteLine(value)
        End Set
    End Property

    ' get, set 毎のアクセス修飾子
    Public Property RWValue As Integer
        Get
            Return 0
        End Get
        Private Set(value As Integer)
            Console.WriteLine(value)
        End Set
    End Property

   ' 引数付きプロパティ
    Public Property ParamValue(ByVal index As Integer) As String
        Get
            Return index.ToString()
        End Get
        Set(value As String)
            Console.WriteLine("[{0}] = {1}", index, value)
        End Set
    End Property

    ' インデクサ
    Default Public Property Item(ByVal index As Integer) As String
        Get
            Return index.ToString()
        End Get
        Set(value As String)
            Console.WriteLine("[{0}] = {1}", index, value)
        End Set
    End Property

    ' 自動プロパティ
    Public Property AutoValue As Integer

    ' 自動プロパティの get, set 毎のアクセス修飾子 -> 不可
    ' Public Property RAutoValue As Integer
    '     Get
    '     Private Set
    ' End Property

    ' 自動プロパティの初期化
    Public Property IAutoValue As Integer = 1

    ' 自動プロパティのバッキングフィールドへのアクセス -> 直に可能
    Public Sub AccessToAutoValue()
        Console.WriteLine(_AutoValue)
    End Sub
End Class

まとめ(感想のようなもの)

get, set 毎のアクセス修飾子を設定できる C# のプロパティは、とても便利。
しかもインデクサ、自動プロパティでも可能!!
C# は、自動プロパティで get, set 毎のアクセス修飾子を設定できる点が便利。
ただ、インデクサ以外の引数付きプロパティは使えない・・・残念。

対する VB の利点は、引数付きプロパティが使えること、自動プロパティが初期化できること。
欠点としては、バッキングフィールドに簡単にアクセスできること。
バッキングフィールドと同じ名前のメンバが定義できず、地味に不便。

おまけ

C# から VB の引数付きプロパティへのアクセスは下記で可能。
・・・ダサい。

var instance = new VbProperties();
// getter
var temp = instance.get_ParamValue(1);
// setter
instance.set_ParamValue(1, "a");

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

参考URL

2013年6月19日

Dim と var

意味は異なるが、型推論の導入により、C#.NET と VB.NET で同じような構文で書けるようになった。
ただ、Dim の方が複数宣言できるので便利。元々、複数宣言可能だったのが、型推論でさらに便利になったというか。
対してvarは複数宣言不可。
理由は、「var val1=1, val2=1.0;」などの場合に型が1つに推測できないから?
複数の型が推測される場合は複数行に展開してくれたら便利だよね・・・

  • Dim (VB.NET)

'[Option Infer On]設定
Dim num = 1 'Integer型
Dim text = "1" 'String型

'複数宣言可能
Dim val1 = 1, val2 = 2, val3 = "1"
  • var (C#.NET)

var num = 1; //int型
var text = "1" //string型

//複数宣言 → コンパイル不可
var val1 = 1, val2 = 2;

Dim の変数宣言+New

Dim に型推論機能が追加されて、変数宣言+Newの省略記法が増えた。
どちらを使うかは好みの問題(生成されるMSILは同じ)だが、クラスのメンバ変数の場合は「As New」しか使えない。
※「As New」は、メンバ変数宣言でも使える省略記法であり、C# より便利な点。

Private Sub Test
    '今までの方法「As New」
    Dim hoge1 As New Hoge()
    '型推論「= New」 ※[Option Infer On]設定
    Dim hoge2 = New Hoge()
End Sub

'メンバ変数で使用可能 (実質、型が省略できる)
Private _hoge1 As New Hoge()

'コンパイル不可(メンバ変数は型推論不可)
Private _hoge2 = New Hoge()

2013年6月2日

キャストの比較 #2 オーバーフロー

キャストまたは算術演算(+、-、*、/) でオーバーフローが発生した際の挙動は、2種類ある。
  1. OverflowException をスロー
  2. 結果の最上位ビットを破棄

どちらの挙動にするかは、プロジェクト単位の設定、または C# なら checkedunchecked 構文が使える。
逆に言えば、VB.NET はプロジェクト単位でしか、オーバーフロー時の挙動を制御できない。
また、プロジェクト単位の設定オプションは下表になるが、注意すべきは C# と VB.NET で設定するチェックボックスの意味が逆という点。
デフォルト設定は両方チェック OFF、つまり、C# はオーバーフロー時の例外を発生させない、VB.NET は発生させる、と逆の挙動になっている。
オーバーフロー時の挙動設定オプション (Visual Studio 2010) デフォルト設定
C#.NET プロジェクトのプロパティ → [ビルド]タブ → [詳細設定]ボタン
→ [演算のオーバーフローおよびアンダーフローのチェック]チェックボックス
チェック OFF
VB.NET プロジェクトのプロパティ → [コンパイル]タブ → [詳細設定コンパイル オプション]ボタン
→ [演算のオーバーフローのチェックを解除]チェックボックス
チェック OFF

定数値のオーバーフロー

定数値のキャスト・算術演算でオーバーフローが発生する場合、コンパイルの挙動が C# と VB.NET で異なる。
オーバーフローチェック無し オーバーフローチェック有り
C#.NET チェック有無に関わらず、コンパイルエラー。
unchecked 構文を使うとコンパイルできる。
VB.NET キャストはコンパイル可能。
算術演算はコンパイルエラー。
キャスト・算術演算ともにコンパイルエラー。
  • C# の例

//定数値のキャストでオーバーフロー → 要 unchecked
int val1 = (int)2147483648L;
//定数値の算術演算でオーバーフロー → 要 unchecked
int val2 = 2147483647 + 1;
  • VB.NET の例

'定数値のキャストでオーバーフロー → オーバーフローチェック解除でコンパイル可
Dim val1 As Integer = CType(2147483648L, Integer);
'定数値の算術演算でオーバーフロー → 常にコンパイル不可
Dim val2 As Integer = 2147483647 + 1;

MSIL比較

オーバーフローチェック有無による MSIL の相違点は、「.ovf」の有無。
例:数値型から int にキャストする場合
オーバーフローチェック無し conv.i4
オーバーフローチェック有り conv.ovf.i4

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

2013年5月30日

キャストの比較 #1 対比表

C#.NET VB.NET
(Type)value DirectCast(value, Type) 基本のキャスト。value の型と Type に継承関係がある場合にキャスト可能。
継承関係ではないが、列挙型とその基になる型の場合、相互にキャスト可能。
C#のみ、下記が可能。
  • 数値型の間で相互変換
  • 列挙型と数値型の間で相互変換(規則は数値型同士の変換と同じ)
  • operator による独自の実装
× CType(value, Type) VB 系の CInt や CStr を型指定して変換できるようにしたもの。
列挙型と数値型の変換も可能。(規則は数値型同士の変換と同じ)
C# の (Type)value と同じく Operator による独自の実装が可能。
ただし、C# の (Type)value の結果と異なることがあり(☆1)、数値型⇔文字列型の変換も可能なことから別物と考えるべき。
value as Type TryCast(value, Type) キャスト失敗時に例外発生ではなく、null を返してくれるキャスト。
そのため、Type は null を代入できる参照型となるが、C# のみ、null 許容型も指定可能。

☆1 C# の (Type)value と VB の CType(value, Type) の結果が異なるパターン
 ※全パターン探ってないため、他にもあるかも…
浮動小数点→整数
変換例は★1
(Type)value 0 方向への丸め(切り捨て)。
C や Java のキャストと同じ。
Math.Truncate(value) でも同じ結果だが、型は変えてくれない。
CType(value, Type) 偶数方向へ丸める四捨五入(銀行丸め)。
Convert.ToInt32(value) と同じ。
Math.Round(value) でも同じ結果だが、型は変えてくれない。

★1 浮動小数点→整数の変換例
value(Type)valueCTypeConvert.ToInt32
-2.0-2-2-2
-1.6-1-2-2
-1.5-1-2-2
-1.4-1-1-1
-1.0-1-1-1
-0.60-1-1
-0.5000
-0.4000
0000
0.4000
0.5000
0.6011
1.0111
1.4111
1.5122
1.6122
2.0222

VB.NET の DirectCast、CType の使い分け

CType より DirectCast の方がパフォーマンスがいいため、DirectCast 可能な場合は、DirectCast を使う。
※ボックス・アンボックス化や、列挙型とその基になる型の相互変換など
String へ変換したい場合は ToString()、String から変換したい場合はそのクラス・構造体の Parse()、TryParse() が大抵使えるので CType は避けるべき。
浮動小数点→整数は、Math のメソッドで丸め後に CType が明示的で分かり易いかも。
整数→整数、整数→浮動小数点は、丸めを気にしなくていいので、素直に CType を使う。
Operator で独自実装している場合は、当然 CType を使用。

VB.NET で C# のキャスト (Double→Integer)

  • Math.Truncate を使う方法
※ Single や Decimal の場合は、引数の型を変えるだけでOK。

Public Shared Function CastToInt(ByVal val As Double) As Integer
    Return CType(Math.Truncate(val), Integer)
End Function

  • MSIL を使う方法
C# で作成した「double 引数を int でキャストして返す」メソッドの MSIL を基に作成。
C# のキャストと同じ結果・挙動になるが、この方法を採るくらいだったら、C# の DLL にキャストメソッドを実装して呼んだ方がまし…

Public Shared Function CastToIntIl(ByVal val As Double) As Integer
    Return DirectCast(_castToIntIlInternal.Value.Invoke(Nothing, New Object() {val}), Integer)
End Function
Private Shared _castToIntIlInternal As New Lazy(Of DynamicMethod)(
    Function()
        Dim instance As New DynamicMethod("CastToIntIlInternal", GetType(Integer), {GetType(Double)}, True)
        Dim il = instance.GetILGenerator()
        With il
            .Emit(OpCodes.Ldarg_0)
            .Emit(OpCodes.Conv_I4) 'キャスト処理(オーバーフローチェック無し)に相当
            .Emit(OpCodes.Ret)
        End With
        Return instance
    End Function
)

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0