starzware

ITスキル

VB.NET

TIPS
VB/Excel定番の関数をVB.netでも使う

'Microsoft.VisualBasicをインポートする
Imports Microsoft.VisualBasic
Strings.Left
Strings.Right
Strings.Mid
[ビルド]OS標準でのMSBuildの場所

[Windows]¥Microsoft.NET¥Framework64¥v4.0.30319¥
[Windows]¥Microsoft.NET¥Framework¥v4.0.30319¥
[Debug]DataSetビジュアライザー

1.クイックウォッチでDataSetを指定する。
2.Tables->Listプロパティを見る。
3.該当列(1つだけしかTableがないなら"(0)"の列)の値列の虫眼鏡をクリックする。
[Debug]DataTableビジュアライザー

1.クイックウォッチでDataTableを指定する。
2.ツリーを展開しないで値列の虫眼鏡をクリックする。
標準出力に出力する

Console.WriteLine("何かを出力")
app.configから値を取得する

'[System.Configuration.dll]が必要
Dim value = System.Configuration.ConfigurationManager.AppSettings("キー")
プロセス起動

'起動する1(ProcessStartInfoオブジェクトあり)
Dim psInfo As New System.Diagnostics.ProcessStartInfo()
psInfo.FileName = "notepad.exe"
Dim process As System.Diagnostics.Process = System.Diagnostics.Process.Start(psInfo)
'起動する2
Dim process As System.Diagnostics.Process = System.Diagnostics.Process.Start("C:\test\1.txt")
'終わるまで待つ
process.WaitForExit()
app.configのサンプル(最小)

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="キー" value="値" />
  </appSettings>
</configuration>
[DataTable]各操作

Dim dt As DataTable = ds.Tables(0)
dt.Columns.Add("column_name") 'column_name列を追加
If IsNothing(dt) OrElse dt.Rows.Count = 0 Then
  'なし(0件を含む)
End If

'Columnループ
For Each col As DataColumn In dt.Columns
Next 'dt.Columns

'Rowループ
For Each dr As DataRow In dt.Rows
  If dt.Columns.Contains("column_name") Then
    '列に"column_name"がある場合

    'CStr(dr("column_name"))でも良いが、CStrはDBNullで落ちるらしい
    Dim column_name = dr("column_name").ToString

  End If 'exists column_name
Next 'dt.Rows

'データ追加
Dim dr As DataRow = dt.NewRow
dr.Item("field1") = "field1"
dt.Rows.Add(dr)
'データ追加 1行
dt.Rows.Add(New Object(){""}) 'Columnの数だけ必要

'検索 Select
Dim selectedRows As DataRow() = dt.Select("field1='a'")

[DataTable]各操作その2

'列情報だけをコピー
Dim dt2 = dt1.Clone

'列情報&データをコピー
Dim dt2 = dt1.Copy

'DataSetに繋がっていているDataTableをコピー
Dim ds1 As New DataSet
Dim dt1 As New DataTable("dt1")
ds1.Tables.Add(dt1)
Dim dt2 As DataTable
'dt2 = dt1 だと ds1 に繋がっているのでNG
dt2 = dt1.Copy

'DataRow単位(丸ごと)コピー
dt1 (データが入っている前提)
Dim dt2 = dt1.Clone
'dt2.Rows.Add(dt1.Rows(0)) は繋がっているのでNG
dt2.ImportRow(dt1.Rows(0))


ファイル削除

System.io.File.Delete(削除するファイル) 'ファイルがなくてもエラーにならない
Option

Option Strict On '暗黙的な型変換を禁止する
Option Infer On  '型推論を有効にする(StrictをOnにしていても大丈夫)
[DataTable]移替え

  Dim convertMap As Hashtable = New Hashtable From {
    '取得先, 取得元
    {"a","A"},
    {"b","B"},
  }
  Private Function ConvDatatable(ByVal pConvMap As Hashtable, ByVal pDt As DataTable) As Datatable
    Dim newDt As DataTable = New Datatable
    '+-- 列を追加
    For Each key In pConvMap.Keys
      newDt.Column.Add(key)
    Next
    '+-- 移し替え
    For Each dr As DataRow In pDt.Rows
      Dim newDr As DataRow = newDt.Rows.Add
      For Each key In pConvMap.Keys
        Dim from_key = pConvMap(key).ToString
        If(pDt.Column.Contains(from_key))Then
          newDr(key) = dr(from_key)
        End If
      Next
    Next
    Return newDt
  End Function
[関数]DataTableからCSVファイル作成

  Public Sub BuildCsvFileFromDataTable(ByVal pFilepath As String, ByVal pDt As DataTable, Optional ByVal isHeader As Boolean = False)
    Using(writer = StreamWrtier(pFilepath, True))
      Dim output = New StringBuilder
      Dim first = True
      '+-- 
      If(isHeader)Then
        output = New StringBuilder
        For Each col In pDt.Columns
          If(first)Then
            output.Append(col.Name)
            first = False
          Else
            output.Append(",").Append(col.Name)
          End If
        Next
        writer.WriteLine(output.ToString)
      End If

      '+--
      For Each dr As DataRow In pDt.Rows
        output = New StringBuilder
        first = True
        For idx As Integer = 0 To pDt.Columns.Count -1
          If(first)Then
            output.Append(dr(idx))
            first = False
          Else
            output.Append(",").Append(dr(idx))
          End If
        Next
        writer.WriteLine(output.ToString)
      Next 'pDt.Rows
      writer.Flush
    End Using
  End Sub
ファイルパスを構成する

'+-- ファイルパスを構成する[folder¥file(win) or folder/file(win以外)]
Dim filepath = IO.Path.Combine(folder, file)
CSVファイル(TextFieldParser)

'-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
' CSVファイルの読み込み
' デリミタを変更すれば[TAB]でも可能
' 以下のデータでも問題なく(3列で)読み取りできる
' "aaa",bbb,"cc,cc"
' データに改行が入る場合は[CSVHelper]に変更することを検討する
'-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Import Microsoft.VisualBasic.FileIO
Dim filepath = IO.Path.Combine("folder", "filename")
Using reader As New TextFieldParser(filepath)
  reader.Delimiters = New String() {","}
  While Not reader.EndOfData
    Dim fields = reader.ReadFields()
  End While
End Using
ファイル操作


'+-- ファイル存在チェック
If IO.File.Exists(filepath) Then
  'ファイルがあるとき
End If

'+-- ファイル削除
System.IO.File.Delete(filepath)             'ファイルがないとエラー
My.Computer.FileSystem.DeleteFile(filepath) 'ファイルがないとエラー
New System.IO.FileInfo(filepath).Delete()   'ファイルがなくてもエラーにならない

'+-- ファイルコピー
IO.File.Copy(fromfile, tofile)

'+-- ファイル移動(名称変更も同様)
IO.File.Move(fromfile, tofile)

XML読み込み(LINQバージョン)

' XMLの読み込み
Dim xmlElement As XElement = XElement.Load("hogehoge.xml")

' LINQのクエリ(hogeで始まるName)
Dim query = From elem In xmlElement.Elements("Userinfo") Where elem.Element("Name").Value Like "hoge*"
' 結果表示
For Each elem In query
    Console.WriteLine($"name:{elem.Element("Name").Value}, age{elem.Element("Age").Value}")
Next

' LINQのクエリ(Ageが30より大きい)
query = From elem In xmlElement.Elements("Userinfo") Where Integer.Parse(elem.Element("Age").Value) > 30
' 結果表示
For Each elem In query
    Console.WriteLine($"name:{elem.Element("Name").Value}, age{elem.Element("Age").Value}")
Next
[関数]CSV2DataTable

Import Microsoft.VisualBasic.FileIO
Function ConvCsvFileToDataTable(ByVal pFileName As String) As DataTable
  Dim dt As DataTable = New DataTable
  Using reader As New TextFieldParser(pFileName)
    reader.Delimiters = New String() {","}
    Dim first As bolean = True
    While Not reader.EndOfData
      Dim fields = reader.ReadFields()
      If first Then
        'ヘッダ行
        Dim index As Integer = 0
        For Each s In fields
          index = index + 1
          If Not String.IsNullOrEmpty(s) AndAlso Not dt.Columns.Contains(s) Then
            dt.Columns.Add(s)
          Else
            dt.Columns.Add("field" & index)
          End If
        Next
      Else
        'データ行
        Dim dr As DataRow = dt.Rows.Add
        Dim index As Integer = 0
        For index = 0 To fields.Count - 1
          dr(index) = fields(index)
        Next
      End If
    End While
  End Using

  Return dt

End Function
Moduleサンプル(with log4net)


Imports System.IO
Imports System.Text

<Assembly: log4net.Config.XmlConfigurator(ConfigFileExtension:="log4net", Watch:=True)<
Module Sample
  Private ReadOnly logger As log4net.ILog = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)

  Sub Main(ByVal args As String())
    logger.Info("=== 処理開始 ===")

    '+--
    If(args.Length = 0) Then
      '+-- 引数なしは終了
      System.Environment.ExitCode = 0
      Return
    End If

    '+-- EXEパスを取得
    Dim currentDir As String = Directory.GetParent(System.Reflection.Assembly.GetExecutingAssembly().Location).ToString

    System.Environment.ExitCode = 0
    logger.Info("=== 処理終了 ===")
  End Sub

End Module
vbprojサンプル

<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <!-- 設定 -->
    <PropertyGroup>
      <AssemblyName>アプリケーション名</AssemblyName>
      <RootNamespace>ルートのNamespace名</RootNamespace>
      <OutputType>Exe</OutputType>
      <Platform>AnyCPU</Platform>
      <OutputType>Exe</OutputType>
      <Configuration>Debug</Configuration>
      <OutputPath>bin</OutputPath>
      <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
    </PropertyGroup>

    <!-- デフォルトimports -->
    <ItemGroup>
      <Import Include="Microsoft.VisualBasic" />
      <Import Include="System" />
      <Import Include="System.Diagnostics" />
    </ItemGroup>

    <!-- 参照設定 -->
    <ItemGroup>
      <Reference Include="System" />
      <Reference Include="System.Data" />
      <Reference Include="log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
        <SpecificVersion>False</SpecificVersion>
        <HintPath>bin¥log4net.dll</HintPath>
      </Reference>
    </ItemGroup>

    <!-- リソース(プログラムソースなど) -->
    <ItemGroup>
      <Compile Include="プログラム.vb" />
    </ItemGroup>

    <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />

  </Project>  
[発行]を行うためのprojectファイル

XXXX_Publish.xmlに発行の設定が出力される。
参照渡ししても呼び元に値が反映されないケース

参照渡しはポインタを渡します。
呼び出し先でポインタを変えてしまうと、呼び元には反映されません。

'呼び出し
Dim ds As New DataSet 'ポインタA
Call subA(ds)

Sub subA(ByRef pDs)
  Dim tmpDs As New DataSet 'ポインタB
  tmpDs.Tables.Add(New DataTable)
  pDs = tmpDs '引数への値の代入(ポインタの変更)はできない
  '以下は反映される(ポインタAが変更されていないから)
  'pDs.Tables.Add(New DataTable)
End Sub

ByRefでオブジェクト型を渡した際にできることとできないこと
できる
 プロパティ変更、メソッド呼び出し
できない
 新しいインスタンスの割り当て(ポインタの変更)
 ※たちが悪いことにコンパイルエラーにならない

上記で理解できない方は以下のサイトもご参照ください。
https://dobon.net/vb/dotnet/beginner/byvalbyref.html#google_vignette

残念なことにMSの解説は配列の説明までは記載されていますが、
オブジェクト型の場合にどのようになるのかは解説されていません。
配列を説明していることによって、逆にオブジェクト型の説明も含まれているように見えてしまいます。
しかも、この配列の説明で全体を代入(a = k)しているのでタチが悪いです。
https://learn.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/language-features/procedures/how-to-change-the-value-of-a-procedure-argument

リフレクション

[DLLロード]
Dim a As [Assembly] = Refrection.[Assembly].Load("DLL名")

[型]
Dim t As Type = a.GetType("DLL名.クラス名")

[メンバに設定]
t.Invoke("メンバ", BindingFlag, Nothing, <クラスインスタンス>, New Object() {"値"})

[メソッド呼び出し]
t.Invoke("メソッド名", InvokeMethod, Nothing, <クラスインスタンス>, New Object(){})