私が文書化しているアプリケーションを熟読しているときに、オブジェクトのプロパティ/メソッドなどにアクセスする際のbang表記の例に出くわしました。他の場所では、同じ目的のように見えるものにドット表記を使用しています。
どちらか一方を使用することに違いや好みはありますか?いくつかの単純なグーグルは、主題に関する限られた情報しか明らかにせず、反対の場合に実際にそれを使用する人もいます。おそらく、狂気の方法を示すMSのコーディング標準セクションがどこかにありますか?
この質問に対する(以前は)受け入れられた回答にもかかわらず、バングは実際にはメンバーまたはコレクションアクセスオペレーターではありません。これは、1つの単純で具体的なことを行います。bang演算子は、bang演算子に続くリテラル名を文字列引数としてそのデフォルトメンバーに渡すことにより、オブジェクトのデフォルトメンバーへのレイトバウンドアクセスを提供します。
それでおしまい。オブジェクトはコレクションである必要はありません。と呼ばれるメソッドやプロパティを持っている必要はありませんItem。必要なのは、最初の引数として文字列を受け入れることができるProperty Getorです。Function
詳細と証拠については、これについて説明している私のブログ投稿を参照してください:The Bang!(感嘆符演算子)VBAで
bang演算子( )は、のプロパティなど、または他の列挙可能なオブジェクトの!メンバーにアクセスするための省略形です。CollectionFieldsADODB.Recordset
たとえば、を作成しCollection、それにいくつかのキー付きアイテムを追加できます。
Dim coll As Collection
Set coll = New Collection
coll.Add "First Item", "Item1"
coll.Add "Second Item", "Item2"
coll.Add "Third Item", "Item3"
このコレクションのアイテムには、次の3つの方法でキーを使用してアクセスできます。
coll.Item("Item2")
これは最も明示的な形式です。
coll("Item2")
これItemは、がクラスのデフォルトメソッドであるため機能するためCollection、省略できます。
coll!Item2
これは、上記の両方の形式の省略形です。実行時に、VB6はバングの後にテキストを受け取り、それをパラメーターとしてItemメソッドに渡します。
人々はこれを本来よりも複雑にしているように思われるので、簡単な説明を見つけるのは難しいのです。通常、複雑さまたは「バング演算子を使用しない理由」は、それが実際にどれほど単純であるかについての誤解から生じます。誰かが強打演算子に問題を抱えているとき、彼らは彼らが抱えている問題の本当の原因ではなく、それを非難する傾向があります。それはしばしばより微妙です。
たとえば、フォームのコントロールにアクセスするためにbang演算子を使用しないことを推奨する人もいます。したがって、よりMe.txtPhoneも優先されMe!txtPhoneます。これが悪いと見なされる「理由」はMe.txtPhone、コンパイル時に正しいかどうかがチェックされるが、そうでMe!txtPhoneはないということです。
最初のケースでは、コードをとして誤って入力しMe.txtFone、その名前のコントロールがない場合、コードはコンパイルされません。2番目のケースでは、を記述した場合Me!txtFone、コンパイルエラーは発生しません。代わりに、を使用したコード行に到達すると、コードは実行時エラーで爆発しますMe!txtFone。
bang演算子に対する議論の問題は、この問題がbang演算子自体とは何の関係もないということです。想定どおりに動作しています。
コントロールをフォームに追加すると、VBは、追加したコントロールと同じ名前のプロパティをフォームに自動的に追加します。このプロパティはフォームのクラスの一部であるため、ドット( "。")演算子を使用してコントロールにアクセスすると、コンパイラはコンパイル時にタイプミスをチェックできます(VBが名前付きコントロールを作成したため、ドット演算子を使用してタイプミスにアクセスできます。あなたのためのプロパティ)。
Me!ControlNameは実際にはMe.Controls("ControlName")1の省略形であるため、コントロール名の入力ミスに対するコンパイル時のチェックが行われないことは驚くべきことではありません。
言い換えると、バング演算子が「悪い」で、ドット演算子が「良い」場合、あなたは考えるかもしれません
Me.Controls("ControlName")
よりも良い
Me!ControlName
最初のバージョンはドットを使用しているためですが、この場合、パラメーターを介してコントロール名にアクセスしているため、ドットはまったく良くありません。コンパイル時のチェックを取得するようにコードを記述する別の方法がある場合にのみ、「より良い」ものになります。これは、VBが各コントロールのプロパティを作成するため、コントロールの場合に発生します。そのため、Me.ControlNameが推奨される場合がありMe!ControlNameます。
Controls私は当初、プロパティがクラスのデフォルトプロパティであると述べていましたが、Davidはコメントで、のデフォルトプロパティではないFormことを指摘しました。実際のデフォルトプロパティは、の内容を含むコレクションを返します。これが、bangの省略形が引き続き機能する理由です。ControlsFormMe.Controlsすでに投稿されている2つの例外的な回答への補遺として役立つカップルの落とし穴:
フォームとレポート
のレコードセットフィールドへのアクセスAccessのフォームオブジェクトのデフォルト項目は、フォームのControlsコレクションとフォームレコードセットのFieldsコレクションの和集合です。コントロールの名前がフィールドの名前と競合する場合、実際にどのオブジェクトが返されるかわかりません。フィールドとコントロールの両方のデフォルトのプロパティはそれら.Valueであるため、多くの場合、「違いのない区別」です。言い換えると、フィールドとコントロールの値は同じであることが多いため、通常はどちらであるかは気になりません。
名前の競合に注意してください!
この状況は、Accessのフォームおよびレポートデザイナが、バインドされているレコードセットフィールドと同じ名前のバインドされたコントロールをデフォルトで使用することによって悪化します。私は個人的に、コントロールタイプのプレフィックスを使用してコントロールの名前を変更する規則を採用しました(たとえば、LastNametbLastNameフィールドにバインドされたテキストボックスの場合)。
レポートレコードセットフィールドはありません!
先ほど、Formオブジェクトのデフォルト項目はコントロールとフィールドのコレクションであると述べました。ただし、Reportオブジェクトのデフォルト項目は、コントロールのコレクションのみです。したがって、bang演算子を使用してレコードセットフィールドを参照する場合は、そのフィールドを(必要に応じて非表示の)バインドされたコントロールのソースとして含める必要があります。
明示的なフォーム/レポートプロパティとの競合に注意してください
フォームまたはレポートにコントロールを追加すると、Accessはこれらのコントロールを参照するプロパティを自動的に作成します。たとえば、という名前のコントロールは、tbLastNameを参照することでフォームのコードモジュールから利用できますMe.tbLastName。ただし、Accessは、既存のフォームまたはレポートプロパティと競合する場合、そのようなプロパティを作成しません。たとえば、Pagesという名前のコントロールを追加するとします。Me.Pagesフォームのコードモジュールで参照すると、「Pages」という名前のコントロールではなく、フォームのPagesプロパティが返されます。
Me.Controls("Pages")この例では、bang演算子を使用して明示的に、または暗黙的に「Pages」コントロールにアクセスできますMe!Pages。ただし、bang演算子を使用すると、フォームのレコードセットに「Pages」という名前のフィールドが存在する場合、Accessが代わりに「Pages」という名前のフィールドを返す可能性があることに注意してください。
.Valueはどうですか?
質問では明確に言及されていませんが、このトピックは上記のコメントで取り上げられました。Fieldオブジェクトとほとんどの「データバインド可能」¹コントロールオブジェクトのデフォルトプロパティ.Valueはです。.Valueこれはデフォルトのプロパティであるため、オブジェクト自体を返すことが意味をなさない場合、VBAはプロパティの値を暗黙的に返します。したがって、これを行うのが一般的な方法です...
Dim EmployeeLastName As String
EmployeeLastName = Me.tbLastName
...これの代わりに...
EmployeeLastName = Me.tbLastName.Value
は文字列であるため、上記の2つのステートメントは同じ結果を生成EmployeeLastNameします。
辞書にキーを設定するときは、微妙な.Valueバグに注意してください
。この規則によって微妙なバグが発生する場合があります。最も注目に値するのは、メモリが機能する場合にのみ、実際に実際に遭遇したのは、フィールド/コントロールの値を辞書キーとして使用する場合です。
Set EmployeePhoneNums = CreateObject("Scripting.Dictionary")
Me.tbLastName.Value = "Jones"
EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-1234"
Me.tbLastName.Value = "Smith"
EmployeePhoneNums.Add Key:=Me.tbLastName, Item:="555-6789"
EmployeePhoneNums上記のコードが辞書に2つのエントリを作成すると予想されるでしょう。代わりに、重複するキーを追加しようとしているため、最後の行にエラーがスローされます。つまり、tbLastNameコントロールオブジェクト自体がキーであり、コントロールの値ではありません。このコンテキストでは、コントロールの値は重要ではありません。
実際、オブジェクトのメモリアドレス(ObjPtr(Me.tbLastName))は、辞書のインデックスを作成するために舞台裏で使用されている可能性が高いと思います。私はこれを裏付けているように見える簡単なテストを行いました。
'Standard module:
Public testDict As New Scripting.Dictionary
Sub QuickTest()
Dim key As Variant
For Each key In testDict.Keys
Debug.Print ObjPtr(key), testDict.Item(key)
Next key
End Sub
'Form module:
Private Sub Form_Current()
testDict(Me.tbLastName) = Me.tbLastName.Value
Debug.Print ObjPtr(Me.tbLastName); "..."; Me.tbLastName
End Sub
上記のコードを実行すると、フォームを閉じて再度開くたびに、1つの辞書アイテムが追加されます。レコードからレコードに移動する(したがって、Form_Currentルーチンを複数回呼び出す)と、新しいディクショナリ項目は追加されません。これは、コントロールの値ではなく、ディクショナリにインデックスを付けるコントロールオブジェクト自体であるためです。
私の個人的な推奨事項/コーディング規約
長年にわたり、私は次のプラクティスを採用してきました、YMMV:
tbTextBoxなどlblLabel)を付けます。Me.(例Me.tbLastName:)Me!との競合などがある場合は、表記を使用しますMe!Pages.Value(例:辞書キー)¹ 「データバインド可能」コントロールとは何ですか?
基本的に、ControlSourceTextBoxやComboBoxなどのプロパティを持つコントロール。バインドできないコントロールは、LabelやCommandButtonのようなものです。TextBoxとComboBoxの両方のデフォルトのプロパティは.Value;です。ラベルとコマンドボタンにはデフォルトのプロパティはありません。