BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Visual Basic 15の新たな言語機能

Visual Basic 15の新たな言語機能

ブックマーク

原文(投稿日:2017/04/11)へのリンク

Visual Basic 15に、C#の2つの重要な機能が部分的に実装される - タプル(tuple)と参照戻り値だ。いずれの機能も“完全”ではないが、これらの機能を利用したC#ライブラリをVBアプリケーションで使用するための回避策としては十分なものだ。

タプル

1回の関数呼び出しで複数の値を直接返す機能は、VBに長い間求められていたものだ。ByRefパラメータを使用して同じ結果を得ることは可能だが、関数型プログラミング言語で見られるものに比べると扱いにくい構文になる。

“タプル”とは要するに、関連する値の集合である。.NET 4.0以降のVisual Basicには標準でTupleクラスが用意されているが、そのユーザエクスペリエンスは満足のいくものには程遠い。Item1やItem2のような意味のない名称を使って、タプルに格納された値を手作業で取り出さなくてはならないのだ。

それから7年を経たVB15で、ようやくタプルの構文サポートが追加された。 ヒープ割り当てされたTupleオブジェクトよりもパフォーマンスの優れたValueTuple構造体も新たに設けられた。新形式を使用して作成したTryParseメソッドの例を示す。

Public Function TryParse(s As String) As (Boolean, Integer)
    Try
        Dim numericValue = Integer.Parse(s)
        Return (True, numericValue)
    Catch
        Return (False, Nothing)
    End Try
End Function

Dim result = TryParse(s)
If result.Item1 Then
    WriteLine(result.Item2)
End If

この例では、TryParseの戻り値の型はValueTuple<Boolean, Integer>である。これにより、ValueTuple型を明示的に指定する必要がなくなることで、関数は多少洗練されたものにはなるが、見て分かるとおり、呼び出すコードは変わっていない。そこで、戻り値のフィールド名を変えて、もう少し改良してみよう。

Public Function TryParse(s As String) As (IsInteger As Boolean, Value As Integer)

End Function

Dim result = TryParse(s)
If result.IsInteger Then
    WriteLine(result.Value)
End If

次の構文を使用して、名前付きフィールドを持つタプルを新たに定義できる。

Dim kvPair = (Key := 5, Value := "Five")

残念ながらVBでは、タプルを複数の変数に“アンパック”する方法はない。従って次のC#コードをVBに変換するには、変数ごとに1行が必要になる。

var (key, value) = kvPair;

(By)Ref 戻り値

参照戻り値はVBではByRefリターンと呼ばれ、制限が多い。フィールドあるいは配列インデックスへの参照(つまりマネージポインタ)を返すC#関数の使用は可能だが、独自に作成することはできないのだ。byRef変数をローカルに定義することもできない。

可能なのは、やや複雑な回避策である。前述のようにByRef変数をローカルに生成することはできないが、ByRefパラメータを定義することは可能だ。このC#シグネチャと、それと同等のVB表記を考えてみよう。

public ref string FindNext(string startWithString, ref bool found)
ByRef Function FindNext(startWithString as string, ByRef found as Boolean) As String

これを使用するには、Klaus Löffelmann氏が示すようなヘルパ関数が必要だ。

    Private Function VbByRefHelper(Of t)(ByRef byRefValue As t,
                                               byRefSetter As Func(Of t, t)) As t
        Dim orgValue = byRefValue
        byRefValue = byRefSetter(byRefValue)
        Return orgValue
    End Function

その上で、参照を返す関数の結果をヘルパ関数のパラメータとして、実行したい内容を別の無名関数として、それぞれ渡す。

  Dim didFind As Boolean

  'Version #2: With a simple generic helper-class:
  aSentence = New NewInCS2017.Sentence("Adrian is going to marry Adriana, because Adrian loves Adriana.")
  Do
     VbByRefHelper(aSentence.FindNext("Adr", didFind),
                           Function(stringFound) As String
                               If stringFound = "Adrian" Then
                                   stringFound = "Klaus"
                                   Return stringFound
                               End If
                               Return stringFound
                           End Function)
  Loop While didfind

参考までに、VBが参照戻り値を完全に実装すれば、次のようなものになるはずだ。

    'THIS DOES NOT WORK IN VB!!!
    Dim aSentence = New Sentence("Adrian is going to marry Adriana, because Adrian loves Adriana.")
    Dim found = False
    Do
        ' !!In C# we can declare a local variable as ref - in VB we cannot.!!
        '   This variable could take the result...
        Dim ByRef foundString = aSentence.FindNext("Adr", found)
        If foundString = "Adrian" Then
            ' but via the reference, so writing would be possible as well,
            ' and we had a neat find and replace!
            foundString = "Klaus"
        End If
    Loop While found

ではなぜ、完全な参照ローカル変数を実装しないのか?

現時点では用途がないから、というのが基本的な理由だ。それが必要なものは現在の.NET APIにはなく、機能自体が不要になる可能性も否定できない。これには前例がある。例えばunsafeブロックは、ネイティブなCライブラリを使用する場合でさえ、ほとんどの場合において使用されていない。スタック割り当て配列についても同じことが言える。

Anthony Green氏が次のように書いている。

VB2017の参照戻り値メソッド利用に関する機能は、この機能をメインストリームに持ち込むためではなく(C#でさえ、そうではありません)、不確実な未来に対するVBの危機回避策として設計されたものです。

[…]

C#の今回のリリースに注目すると、この機能に関連してもうひとつの機能、すなわち参照ローカル変数(ref locals)があることに気付きます。参照ローカル変数に関しても、再割り当て(ローカル‘ポイント’を別のロケーションにすること)、デフォルトで読み取り専用にするべきか、変数定義の可能なすべての場所における参照の指定方法、参照設定と通常の設定を区別する方法など、さまざまな検討と決定が行なわれています。これらすべてをVB内で解決した上で、同等の生産性を実現しなくてはなりません。それには多くの作業が必要です。

しかもVBでは、プロパティのByRef渡しのようなByRefに関わる特別な機能があるため、さらに多くの処理が必要になります。この件に関しては、VBコンパイラの開発責任者であるJared Parsons氏が、VBのByRefに関するさまざまなケースを詳しく解説した素晴らしい記事を公開しています。プロパティをByRefで返したり、遅延バインディングのコンテキストでByRefを扱うことに意味があるとは思えませんから、ByRef戻り値とByRefパラメータの動作はおのずと異なるものになり、(C#におけるすべての問題点とともに)事態をさらに複雑にしています。(Jaredの記事もアップデートして、さらにケースを加えなければならないでしょう。)ひとつのコレクション型を書くために、いつ使用されるのか分からないような機能をC#が追加したことで、VBに多くの混乱と構文的、概念的オーバーヘッドが加わる結果になったのです。

私たちは別のアプローチを選択しました。簡単に言うと、配列で実行可能なSlice(あるいは参照を返すもの)に関する操作を正しく行なう上で必要な使用シナリオだけを実装して、それ以上のことは行なわなかったのです。インデックスを指定すれば配列要素に直接設定することが可能ですが、参照を配列要素からローカルに設定した後に、配列に戻すことはできません。また、そのインデックスの値をインプレースで変更してByRefで渡すことは可能ですが、それをByRefで返すことはできません。つまり、もしVS2017リリースと言語の次バージョンの間でスライスを加えたならば、新たな構文とVBはすべて保護されなくなる、ということです。すべてのコレクション型がスライスに置き換えられることで、数限りないAPI呼び出しにおいて、ByRefによる値返しがすべてエラーを吐き続けるようになるでしょう。そして、そのあり得ないような悪夢のシナリオが起きなければ、それによって言語が傷を負うこともないのです。そのような設計方法が採られている理由はそこにあります。

(前述のリンクで詳しい説明を読むことができる。)

このような対処を行なう言語はVBだけではない。ASP.NET MVC開発者たちが強く望んでいたXMLリテラルのサポートは、同じような懸念から、C#ではサポートされないことになった。

 
 

この記事を評価

関連性
スタイル
 
 

この記事に星をつける

おすすめ度
スタイル

BT