ふにゃるんv2

もとは、http://d.hatena.ne.jp/Wacky/

C/C++/C#言語の副作用完了点を、Visual Basic .NETで記述する際の注意点

自分は、仕事柄 C言語系に慣れている人でして、以下のような記述を(無意識に)するケースが多いんですね。

char* strMessage = NULL;
...
if((strMessage != NULL) && (strlen(strMessage) > 0)) ...

ここで、上のif文の「&&」は「副作用完了点」と呼び、左の式が真にならないと、右の式が実行されない訳です。(詳しくは、C言語FAQやK&R聖典を読んで下さい)


C言語上がりの自分がBasic言語に触れて、驚いた事の一つに、上の記述が通用しなかった点なんですね。

Dim strMessage As String = Nothing
...
If (strMessage IsNot Nothing) And (strMessage.Length > 0) Then ...

はい、こんな書き方ではエラーが発生します。
少なくとも、以下のように書く必要があります。

If strMessage IsNot Nothing Then
  If strMessage.Length > 0 Then
    ...

驚いたものの、引っかかった当時「Basicって どういう評価順番なのかなぁ」と思いつつ、特に突っ込んで調べもせず、そのまま放っておいてました。(ダメ人間ですね)


で、先日マイクロソフトさんの登録されているMSKBを ぼ〜っと眺めていたら、以下の記事が。

Visual Basic 2005 または Visual Basic .NET では、論理 AND 式および論理 OR 式のオペランドは、1 つ目のオペランドの結果に関係なく、2 つ目も評価されます。
...
ただし、Visual Basic 2005 または Visual Basic .NET には、新しい 2 つの演算子 (AndAlso 演算子および OrElse 演算子) があります。これらの演算子を、それぞれ論理 AND 演算子および論理 OR 演算子の代わりに使用できます。これらの演算子を使用すると、Visual Basic .NET で "ショートサーキット" 評価を行うことができます。


えぇぇえ??そうだったの?
この「ショートサーキット評価」って要するに、「副作用完了点」と同じ事だよね?


という訳で、早速検証。

Imports CON = System.Console

Function func(ByVal msg As String) As String
    CON.WriteLine("call func({0})", msg)
    Return msg
End Function

Sub Main()
    CON.WriteLine("普通のAndテスト")
    If (func("左辺") <> "左辺") And (func("右辺") <> "右辺") Then CON.WriteLine("両方一致")

    CON.WriteLine("AndAlsoテスト")
    If (func("左辺") <> "左辺") AndAlso (func("右辺") <> "右辺") Then CON.WriteLine("両方一致")

    CON.WriteLine("IIfテスト")
    CON.WriteLine("IIf = {0}", IIf(True, func("TRUE"), func("FALSE")))

End Sub

上のコードを実行すると、以下のようになります。

普通のAndテスト
call func(左辺)
call func(右辺)

AndAlsoテスト
call func(左辺)

IIfテスト
call func(TRUE)
call func(FALSE)
IIf = TRUE

なるほど。確かに「And演算子」を使わず「AndAlso演算子」を使うと、左辺のみ評価可能ですね。
ちなみに、下のIIf関数の例は、三項演算子の代用として時折使うので、念の為 動作を確認してみたんですね。
案の定、ただの関数なので、「引数に記述した関数は当然実行」されます。


ちなみに、C言語系であるC#で記述すると、以下のようになります。

using CON = System.Console;

string func(string msg)
{
    CON.WriteLine("call func({0})", msg);
    return msg;
}

public void test()
{
    CON.WriteLine("andテスト");
    if ((func("左辺") != "左辺") && (func("右辺") != "右辺")) CON.WriteLine("両方一致");

    CON.WriteLine("三項演算子");
    CON.WriteLine(" = {0}", (true ? func("true") : func("false")));
}

上のコードを実行すると、以下のようになります。

andテスト
call func(左辺)

三項演算子
call func(true)
 = true

よしよし、C言語と同じ動きです。


更に、Pythonで記述すると、以下のようになります。

# -*- encoding: shift-jis -*-

def func(msg):
	print "call func(%s)" % msg
	return msg

print "andテスト"
if (func("左辺") != "左辺") and (func("右辺") != "右辺"):
	print "両方一致"

print "Python2.5から使える三項演算子"
ret = func("true") if True else func("false")
print ret

上のコードを実行すると、以下のようになります。

andテスト
call func(左辺)

Python2.5から使える三項演算子
call func(true)
true

う〜ん、Python三項演算子には慣れないなぁ。

とはいえ

言語の期待する書き方に合わせた方が楽ですよね。
変にアクロバットな書き方は、リスクが高いので気をつけましょうという事です。(これがオチ)