佐々木屋

技術的なことから趣味まで色々書きます

オーバーライドされた基底クラスメソッドを呼び出す

派生クラスで基底クラスメソッドをオーバーライドされた場合、処理は派生クラス側で上書きされてしまいますが、別に基底クラスメソッドが消えるわけではなくちゃんと呼び出すことが可能です。基底クラスのメソッドを呼ぶ場合はbaseキーワード(VB.NETはMyBase)を使用します。

public class BaseClass {
    public virtual void ShowMessage() {
        Console.WriteLine("基底クラス");
    }
}

public class SubClass : BaseClass {
    public override void ShowMessage() {
        Console.WriteLine("派生クラス");
        base.ShowMessage();
    }
}
Public Class BaseClass
    Public Overridable Sub ShowMessage()
        Console.WriteLine("基底クラス")
    End Sub
End Class

Public Class SubClass
    Inherits BaseClass
    Public Overrides Sub ShowMessage()
        Console.WriteLine("派生クラス")
        MyBase.ShowMessage()
    End Sub
End Class
基底クラス
派生クラス
基底クラス

null許容型

値型がnullを許容しないことは分かりました。しかし、アプリケーション構築上、nullを許容しなければいけない場面もあります。そこで登場したのがnull許容型です。

null許容型は値型の後ろに「?」をつけるだけです。しかし、元々許容型である参照型やstring型は「?」をつけることができません。

int? a = null;
decimal? b = null;
Dim a As Integer? = Nothing
Dim b As Decimal? = Nothing



null許容型はコンパイルされると、結果的にNullable構造体となります。つまり、以下変数a、bは意味としては同じということになります。

int? a = null;
Nullable<int> b = null;
Dim a As Integer? = Nothing
Dim b As Nullable(Of Integer) = Nothing

CodeBehindによるファイル追加で発生する問題

ASP.NETのプロジェクトファイルに既存ファイルを追加する場合、通常はVisualStudioのソリューションエクスプローラーから追加します。しかし、状況によってはOSのエクスプローラー上でファイルの移動をする場合があります。

この場合、ASP.NETのプロジェクトがCodeFileで作成してあれば特に問題になることはありませんが、CodeBehindで作成している場合は問題になります。CodeFileは動的コンパイルによって構成ファイルをVisualStudioではなく、ビルド、発行時にファイルが含まれません。これはCodeFileが動的コンパイラに対し、CodeBehindはビルド処理によるコードファイルのコンパイラとなっている為です。

つまり、CodeBehindはあらかじめビルドしたアセンブリ(dll)に対してWEBサーバーが応答する形ということです。


これを回避するためには、追加した後に以下の手順でファイルをプロジェクトに含める必要があります。

1. エクスプローラを使って、追加したいフォルダをプロジェクトフォルダの中にコピーする

2. ソリューションエクスプローラで「すべてのファイルを表示」をONにする
f:id:sasaki816:20190217202553j:plain

3. フォルダが見えると思うので、それを右クリックして「プロジェクトに含める」
f:id:sasaki816:20190217202608j:plain

null許容型(C#とVB.NETの違い)

参照型はnullを許容していますが、値型はどうでしょうか。実はここでもC#VB.NETで微妙に挙動が異なります。

C#におけるnull代入時の挙動

参照型はnullを許容していますが、値型はnull非許容です。つまり、以下のような構文はそもそもコンパイルエラーとなってしまいます。

int a = null;
decimal b = null;


VB.NETにおけるNothing代入時の挙動

VB.NETのnullはNothingとなります。参照型はC#同様null許容なので何の問題もありませんが、問題は値型です。結論から言えば、C#であろうとVB.NETであろうと値型はnull非許容です。

Dim a As Integer = Nothing
Dim b As Decimal = Nothing

しかしコンパイルが通ってしまいます。何故でしょうか。

VB.NETは暗黙的に初期化されてしまう

以下の構文をそのまま実行してみましょう。

Dim a As Integer = Nothing
Dim b As Decimal = Nothing
Console.WriteLine(a)
Console.WriteLine(b)
0
0

つまり、VB.NETの場合null非許容の値型へNothingを代入すると、内部的に初期化される仕様となります。

VB.NETの値型がnull非許容ということは、以下の構文を書くとよく分かります。

Dim a As Integer = Nothing
If a Is Nothing Then
End If

これは2行目でnull許容に関するコンパイルエラーとなります。

'Is' 演算子は型 'Integer' のオペランドを受け付けません。オペランドは参照型または Null 許容型でなければなりません。




このように、C#VB.NETで挙動が違い、またVB.NETの場合は非常に分かりにくい仕様になっています。

これの何が問題かというと、オブジェクト指向でデータベースを扱ったりクラスを設計した場合、どうしてもnull(要は何もない状態)を返したい場合が出てきます。C#であれば値型はnull非許容なので明示的にnull許容型(別途説明します)にすれば何の問題もありませんが、VB.NETの場合、例えばInteger型だとNothingを代入しても0が返ってきてしまいます。つまり、Nothing代入による初期値0のことなのか、処理結果の0なのかの判断が出来ないということになります。

this参照

this(VB.NETはMe)が一体どういう意味を持っているのか、どういったときに使用するのかを説明します。

そもそも、ローカル変数とフィールド変数の名前がかぶらなければお目にかかることはないわけですが、オブジェクト指向型言語であれば一度や二度はかぶってしまったことがあるのではないでしょうか。

通常名前がかぶった場合はローカル変数が優先されますが、this参照するとフィールド変数を優先してくれます。

public string naiyo = "piyo";
public void Test() {
    string naiyo = "hoge";
    Console.WriteLine(naiyo);
    Console.WriteLine(this.naiyo);
}
Private naiyo As String = "piyo"
Public Sub Test()
    Dim naiyo As String = "hoge"
    Console.WriteLine(naiyo)
    Console.WriteLine(Me.naiyo)
End Sub
hoge
piyo



別に違う変数名にすればと思うかもしれませんが、オブジェクト指向のクラス設計上やむを得ない時があるのです。

なお、this参照は静的クラスでは使用できません。

オーバーライド

よく間違えるNo1のオーバーライドです。「オーバーロード」とよく間違えて使う人がいますが、この二つ全く意味が違いますので混同しないように注意しましょう。オーバーライドはオブジェクト指向の「継承」分野で必要な機能となります。

オーバーライドを簡単に説明すると、「振る舞いを上から(over)塗りつぶす(ride)」というニュアンスで、基底の振る舞いを継承先で変更する場合に使用します。メソッド、プロパティ、インデクサーに使用でき、変数やstaticメンバー、抽象メソッド(abstract)には使用することは出来ません。
今回はメソッドを例としてオーバーライドを説明します。


オーバーライドされる側のメソッドにC#の場合「virtual」、VB.NETの場合「Overridable」キーワードを指定します。このキーワードを指定しないメソッドはオーバーライドすることが出来ません。

public class BaseClass {
    public virtual void ShowMessage() {
        Console.WriteLine("基底クラス");
    }
}
Public Class BaseClass
    Public Overridable Sub ShowMessage()
        Console.WriteLine("基底クラス")
    End Sub
End Class

つまり、派生クラスで勝手に書き換えを禁止する為の措置となります。これらのメソッドを「仮想メソッド」と呼びます。


オーバーライドする側のメソッドはC#の場合「override」、VB.NETの場合「Overrides」キーワードを指定します。このキーワードを指定すると、メソッドを自由に変更することが出来るようになります。

public class SubClass : BaseClass {
    public override void ShowMessage() {
        Console.WriteLine("派生クラス");
    }
}
Public Class SubClass
    Inherits BaseClass
    Public Overrides Sub ShowMessage()
        Console.WriteLine("派生クラス")
    End Sub
End Class



実行は以下のように、派生クラスについては継承元の型でも宣言可能(ポリモーフィズム)です。

BaseClass test1 = new BaseClass();
SubClass1 test2 = new SubClass();
BaseClass test3 = new SubClass();

test1.ShowMessage();
test2.ShowMessage();
test3.ShowMessage();
Dim test1 As New BaseClass()
Dim test2 As New SubClass()
Dim test3 As BaseClass= New SubClass()

test1.ShowMessage()
test2.ShowMessage()
test3.ShowMessage()
基底クラス
派生クラス
派生クラス



よって、以下のようにジェネリックコレクションを利用した宣言も可能となります。

List<BaseClass> test = new List<BaseClass> { new BaseClass(), new SubClass1() };
foreach(BaseClass t in test) {
    t.ShowMessage();
}
Dim test As New List(Of BaseClass) From {New BaseClass(), New SubClass1()}
For Each t As BaseClass In test
    t.ShowMessage()
Next

ボックス化って何?

型変換でよく出てくる用語の一つに「ボックス化(又はボクシング)」があります。よくボックス化はコストがかかるとか言われますが、一体何の意味か分かりますか?

値型を参照型に変換する行為

簡単に言うとそういうことです。例えばint型をobject型に変換することを「ボックス化」と呼ぶわけです。ただこれは少し乱暴な表現で、冷静に考えればint型は値型でobject型は参照型なので型変換が必要なはずです。なぜ型変換せず変換できるのでしょうか。

int n = 10;
object o = n;
Dim n As Integer = 10
Dim o As Object = n

実はint型はobject型の派生なのです。int型に限らず値型はobject型の派生なので、型変換無しでボックス化が暗黙的に(CLRが)行われます。よって我々が意識することはなく内部で自動的に行われますので、気が付かずにボックス化しているなんてことも起こりえます。

ボックス化の逆は明示的に型変換が必要

ボックス化の逆を「ボックス化解除(又はアンボクシング)」と言います。オブジェクトから値型を抽出する行為です。これはボックス化と異なり、型変換を明示的に行う必要があります。なお、Javaの場合はオートボクシング機能がありますので、この部分のみC#Javaでボックス化のニュアンスが異なりますので注意して下さい。

int n = 10;
object o = n;

o = 20;
n = (int) o;
Dim n As Integer = 10
Dim o As Object = n

o = 20
n = CType(o, Integer)


ボックス化(ボックス化解除)はコストがかかります

値型はスタック領域に書かれますが、参照型はヒープ領域に書かれます。当然スタック領域に比べてヒープ領域の方が速度が遅めですので、そこの変換作業によってコストがかかるということになります。

ボックス化

スタック上の値がヒープ上の新しい領域に値が作成され、その参照情報がスタック領域の新しい場所へ格納されます。
f:id:sasaki816:20190302195500j:plain

ボックス化解除

ヒープ領域に作成したボックス化情報を型変換すると、スタック上に新たな領域を確保して値を作成します。
f:id:sasaki816:20190302195529j:plain


厳密に型を指定して宣言した方がよい、というのは不用意なボックス化を防ぐ意味でもあります。