ASP.NET Webフォームアプリケーションをコピーして、新しいアプリケーションを作成する際の注意

ちょっとハマったので自分用メモです。

VisualStudio2013 ASP.NET Webフォームアプリケーションであるプロジェクトを作成しました。
これをAプロジェクトとします。

次にAプロジェクトをコピーしBプロジェクトを作成しました。
アセンブリ名などを変更し、特に問題なく実行できることを確認しました。


次に
まずAプロジェクトを実行し、起動したIEを閉じます。

BプロジェクトのWeb.configを修正し起動させます。 このとき変更後のWeb.configが読み込まれず、AプロジェクトのWeb.configが読み込まれていました。
またBプロジェクトにブレークポイントを置いても、ブレークポイントで止まりませんでした。
ブレークポイントに警告「ブレークポイントは現在の設定ではヒットしません。(以下略)」と表示されていました。

各プロジェクトのプロパティでWebタグの「サーバー」の仮想ディレクトリが同じことが原因でした。

.NET(VB) いまさらだけどVBでYieldを書いてみた

C#ではVisualStudio2005(C#2.0)でyieldが登場し、
VBではVisualStudio2012(VB11)でやっとyieldが使えるようになりました。

「いつか使ってみたいなぁ」と思っていたけれど、まったく使う機会がありませんでした。
もうね「このまま一生使うこともないのかなぁ」と思ってました。
しかし、この度やっとyieldを使ってみることができました!しかもVBで!

うれしかったですね。
「yield便利~♪」より「yieldやっと使えたよ~っ泣」って。

yield便利だけど使う機会がほぼないんですよね。
特に私のような末端の底辺PGにはw
しかもジェネリックの登場で自作コレクションを作る機会自体が減ってますからね。

今回はASP.NETで自作コレクションクラスを作る必要がありyieldを使ってみました。
(少しハマった部分もありました。別記事で書きます。)

yieldの使い方はネットにごろごろあるので自分用のメモ程度に記載しておきます。

VisualStuidio2013/Fw4.0/VB
Public Class MyCollection
    Implements IEnumerable(Of String)

    Private _items As New List(Of String)

    Public Iterator Function GetEnumerator() As IEnumerator(Of String) _
    Implements IEnumerable(Of String).GetEnumerator
        For Each s As String In Me._items
            Yield s
        Next
    End Function

    Public Function GetEnumerator1() As IEnumerator _
    Implements IEnumerable.GetEnumerator
        Return GetEnumerator()
    End Function
End Class

自作コレクションクラスをForEachでクルクルまわせたり、Linqで使えるようにするためにIEnumerable(Of T)を実装させます。
IEnumerable(Of T)を実装すると、GetEnumerator()メソッドとGetEnumerator1()メソッドを実装する必要があります。

GetEnumerator1()メソッドは、もう一方のGetEnumerator()を返すように実装します。
GetEnumerator()メソッドは、Yeildを使用できるようにIteratorキーワードを付けます。
Public Iterator Function GetEnumerator() As IEnumerator(Of …
あとはコレクションの要素をクルクル回して1コづつYeildで返してあげればOKです。

.NET VisualStudioのバージョンとFrameWork、VB、C#の対応表

調べる必要があったのでメモっておきます。

Visual StudioFrameworkVBC#
Visual Studio 2015Framework 4.6 VB14C#6.0
Visual Studio 2013Framework 4.5.1VB12C#5.0
Visual Studio 2012Framework 4.5 VB11C#4.5
Visual Studio 2010Framework 4.0 VB10C#4.0
Visual Studio 2008Framework 3.5 VB9 C#3.0
Visual Studio 2005Framework 2.0 VB8 C#2.0
Visual Studio 2003Framework 1.1VB7.1 C#1.1
Visual Studio 2002Framework 1.0 VB7 C#1.0

Oracle Oracleクライアントのアンインストール

Oracleクライアントのアンインストール方法です。

複数VersionのOracleクライアントをインストールしている開発用PC(Windows8)で、
Oracleクライアントをアンインストールして、再度Oracleクライアントをインストールしたところ
.Netで作成したWindowsフォームアプリで32bitでコンパイルしたものが動作しなくなってしまいました。
同じく.Netで作成した別のWindowsフォームアプリで64bitでコンパイルしたものは動作しました。

使用しているOracle.DataAccess.dllが参照できなくなってしまったようです。

ODP.NETのdll(OracleDataAccess.dll)は、
GACにインストールされている「発行者ポリシー」の仕様により、
アプリケーション開発者が意図しないバージョンのODP.NETが、アプリケーションの実行時に使用されてしまうという問題があります。
詳しくはコチラ「熱燗ロックのブログ ODP.NETのバージョン問題」

Oracleクライアントのアンインストールで、GACに登録されているOracleの発行者ポリーシーが削除されなかった為に
32ビット用のODP.NETを使用したときに、削除されなかった発行者ポリシーが、アンインストールされたODP.NETを使用するようにリダイレクトさせているようです。

そこで複数バージョンのOracleクライアントをインストールした際のアンインストール手順をメモっておきます。

アンインストール手順


Oracleクライントのアンインストール


「すべてのプログラム」 ⇒ 「Oracle - 各オラクルクライアントのバージョン」 ⇒ 「Oracle Installation Produucts」 より
「Universion Installer」を起動します。
開いたダイアログの「製品のアンインストール」ボタンをクリック

削除する製品をチェックして「削除」ボタンをクリック

すると「deinstall.bat」を実行しろと警告メッセージが表示されます。
指示されたバッチファイルを実行します。
(…各Oracleクライアントのdeinstall.batを実行すれば、ここまでの手順は不要だと思います。

バッチを実行すると、結構長い間「Checking for reuiured …略…」と表示されたままですが、しばらくするとアンインストールが始まります。


バッチがおわったら、残った残骸を削除していきます。

ファイルの削除


  • C:\Program Files\Oracle
  • C:\Program Files (x86)\Oracle
  • 各オラクルクライアントをインストールしたフォルダ
  • (インストールフォルダがわからない場合は、後述のレジストリエディタ「HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE」で確認できます。)

スタートメニュー


「すべてのプログラム」でスタートメニューを起動し「Oracle - 各オラクルクライアントのバージョン」を削除

環境変数


「コントロールパネル」 ⇒ 「システム」⇒ 「システムの詳細設定」で「システムのプロパティ画面」を起動し、
「環境変数」ボタンをクリックし、「Path」からOracleを含む変数を削除します。

レジストリ


※削除する前にバックアップを取ってくださいね。
削除するキーは
Oracle10gの情報ですがOracle Database インストレーション・ガイド Oracle Databaseソフトウェアの削除の中ほどに 「Microsoftレジストリ エディタからのOracleキーの削除」に記載されているものを削除しました。

「ファイル名を指定して実行」より「regedit」と入力し「レジストリエディタ」を起動します。
■HKEY_CLASSES_ROOT
Ora、Oracle、OrclまたはEnumOraで始まるキーを削除。
以下のものがあります。
  • EnumOraHomes
  • OracleConfig
  • OracleDatabase
  • OracleHome
  • OracleInProcServer
  • OracleProcess
  • ORADC
  • ORAMMCCFG10
  • ORAMMCPMON10
  • OraOLEDB
  • OraPerfMon
  • ORCLMMC
  • ORCLSSO

■HKEY_CURRENT_USER
ORACLEキーを削除。

■HKEY_CURRENT_USER\Software
Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Start Menu\Programsの下にあるOracle-HOME_NAMEエントリなど、すべてのOracleキーを削除。

■HKEY_LOCAL_MACHINE\SOFTWARE
ORACLE Groupキーを削除。
※ここに記載されている内容ががOracle Universal Installerの場所です。

■HKEY_LOCAL_MACHINE\SOFTWARE\ODBC
ODBCの下にあるサブキーをすべて展開して、Microsoft ODBC for Oracleキーを除くOracle関連のODBCドライバ・キーをすべて削除。

■HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
この下のOracleまたはOraWebで始まるすべてのキーを削除。

■HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application
この下のOracleで始まるすべてのキーを削除。

GACの削除


C:\Windows\Microsoft.NET\assembl\GAC_32
C:\Windows\Microsoft.NET\assembl\GAC_64
C:\Windows\Microsoft.NET\assembl\GAC_MSIL
C:\Windows\assembly
上記のフォルダからOracleとつくファイルを削除していきます。
以下のようなものがあります。
  • Oracle.DatatAccess
  • Oracle.DataAccess.resources
  • Oracle.Web
  • Oracle.Web.resources
  • Policy.XXXXX.Oracle.DataAccess
  • Policy.XXXXX.Oracle.Web
上記のファイルは削除しようとすると「アクセス権限がありません。」となります。
VisualStudio付属ツールのコマンドプロンプトを使用して削除します。(GACからアセンブリを登録解除する)

VisualStudio2013の場合
「スタートプログラム」 ⇒ 「Microsoft Visual Studio 2013」⇒ 「Visual Studio ツール」をクリックし「開発者コマンド プロンプト for VS2013」を右クリック「管理者として実行」

VisualStudio2010の場合
「スタートプログラム」 ⇒ 「Microsoft Visual Studio 2010」⇒ 「Visual Studio ツール」⇒「Visual Studio コマンド プロンプト (2010)」を右クリックし「管理者として実行」


GACからアセンブリを登録解除するコマンドを入力します。
gacutil /u "削除するアセンブリ名,Version=X.XXX.X.X, Culture=XXXXX, PublicKeyToken=XXXXXXXXXXX"
アセンブリの情報はファイルエクスプローラより削除したいアセンブリを右クリックし「プロパティ」で確認できます。

たとえばOracle.DataAccessを削除する場合
gacutil /u "Oracle.DataAccess,Version=2.121.1.0, Culture=Neutral, PublicKeyToken=89b483f429c47342"

以上で削除は終了です。

忘れずにPCを再起動します。

Oracleクライアントを再インストールして、.Netで作成したWindowsフォームアプリ(32bit、64bit)ともに正常に動作しました。

.NET(VB) アプリケーションイベントを使用した際の例外一括処理

WindowsFormアプリケーションです。
VBで「アプリケーションフレームワークを有効にする」をチェックONにしたときに、例外を一括処理する方法です。

C#ではアプリケーションのエントリーポインはMainメソッドになります。
補足されなかった例外を一括で処理する場合、このMainメソッド内で
System.Windows.Forms.Application.ThreadExceptionイベントと
System.AppDomain.CurrentDomain.UnhandledExceptionイベントをハンドルして処理すると思います。

VBでもエントリーポイントをMainメソッドにした場合、C#と同じ方法で補足されなかった例外を一括処理できます。

しかし、VBではデフォルトで「アプリケーションフレームワークを有効にする」チェックONなので
スタートアップフォームを指定してアプリケーションを起動することが多いように思います。

私に権限があれば自作のMainメソッドをエントリーポイントにしてアプリを起動するようにするのですが
(そのほうが細かいこともまで融通が利くので)
政治的な理由で「アプリケーションフレームワークを有効にする」チェックOFFにできない場合
アプリケーションイベントを使用して、補足されなかった例外を一括処理できます。

まずアプリケーションイベントとはVBにのみ用意されている、アプリケーションに関するイベントを記述することができるクラスです。
このクラスファイルはデフォルトでは非表示です。
まずはこのファイルを表示させます。

プロジェクトのプロパティ画面を開き「アプリケーション」タブにある「アプリケーションイベントの表示」をクリックします。

するとプロジェクト内に「ApplicationEvent.vb」ファイルが表示されます。

このファイルの中を見ると
名前空間は「My」クラス名は「MyApplication」になっています。
「Partial」キーワードが付いているので、どこかに分離されたコードがあります。

「My Project」の「Application.myapp」の「Application.Designer.vb」が分離されたコードになります。

MyApplicationクラスのコードのコメントに
「UnhandledException: ハンドルされていない例外がアプリケーションで発生したときに発生するイベントです。」とあるので
一見、これを使えばよさそうな気がします。

結論からいいますと、ダメでした。

例外の補足には
メインフォームが実行されているスレッド(UIスレッド)で発生する例外と
メインフォームが実行されているスレッド以外(UIスレッド以外)で発生する例外を補足する必要があります。

UIスレッドで発生する例外を補足するには
System.Windows.Forms.Application.ThreadExceptionイベントを使用します。

UIスレッド以外で発生する例外を補足するには
System.AppDomain.CurrentDomain.UnhandledExceptionイベントを使用します。

MyApplicationクラスのUnhandledExceptionイベントは、UIスレッドで発生する例外を補足できるようです。

UIスレッド以外で発生する例外を補足するために、StartUpイベントを使用して
System.AppDomain.CurrentDomain.UnhandledExceptionイベントをハンドルしてみました。
以下がダメな場合のコードです。
実行するとUIスレッドで発生した例外もすべて、CurrentDomain_UnhandledExceptionメソッドで処理されてしまいます。

ダメなコードです!!
Namespace My

    ' 次のイベントは MyApplication に対して利用できます:
    ' 
    ' Startup: アプリケーションが開始されたとき、スタートアップ フォームが作成される前に発生します。
    ' Shutdown: アプリケーション フォームがすべて閉じられた後に発生します。このイベントは、通常の終了以外の方法でアプリケーションが終了されたときには発生しません。
    ' UnhandledException: ハンドルされていない例外がアプリケーションで発生したときに発生するイベントです。
    ' StartupNextInstance: 単一インスタンス アプリケーションが起動され、それが既にアクティブであるときに発生します。
    ' NetworkAvailabilityChanged: ネットワーク接続が接続されたとき、または切断されたときに発生します。
    Partial Friend Class MyApplication

        Private Sub MyApplication_Startup(sender As Object, e As ApplicationServices.StartupEventArgs) Handles Me.Startup

            AddHandler System.AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException
        End Sub


        Private Sub MyApplication_UnhandledException(sender As Object, e As ApplicationServices.UnhandledExceptionEventArgs) Handles Me.UnhandledException
            'アプリケーションを終了しない
            e.ExitApplication = False
            '例外メッセージを表示
            MessageBox.Show(e.Exception.Message,"MyApplication_UnhandledException")
        End Sub

        Private Sub CurrentDomain_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs)
            '例外メッセージを表示
            MessageBox.Show(DirectCast(e.ExceptionObject, Exception).Message,"CurrentDomain_UnhandledException")
        End Sub

    End Class


End Namespace
UIスレッド以外のスレッドは使用しないから、UnhandledExceptionイベントだけハンドルすればいいかと思ってたのですが
メインスレッドのコンストラクタ内で例外が発生すると、補足できません。

※コンストラクタで例外が発生するのがどうかと思うのですが、ごくたまにサードパーティ製のコントロールが例外をスローして
例外が補足できずに標準の例外が出てしまうことがありました。


OnInitializeメソッドをオーバーライドして
System.Windows.Forms.Application.ThreadExceptionイベントと
System.AppDomain.CurrentDomain.UnhandledExceptionイベントをハンドルすると、希望通りの動作になりました。
Namespace My

    ' 次のイベントは MyApplication に対して利用できます:
    ' 
    ' Startup: アプリケーションが開始されたとき、スタートアップ フォームが作成される前に発生します。
    ' Shutdown: アプリケーション フォームがすべて閉じられた後に発生します。このイベントは、通常の終了以外の方法でアプリケーションが終了されたときには発生しません。
    ' UnhandledException: ハンドルされていない例外がアプリケーションで発生したときに発生するイベントです。
    ' StartupNextInstance: 単一インスタンス アプリケーションが起動され、それが既にアクティブであるときに発生します。
    ' NetworkAvailabilityChanged: ネットワーク接続が接続されたとき、または切断されたときに発生します。
    Partial Friend Class MyApplication

        ''' 
        ''' OnInitializeメソッドをオーバーライド
        ''' 
        ''' 
        ''' 
        ''' 
        Protected Overrides Function OnInitialize(commandLineArgs As ObjectModel.ReadOnlyCollection(Of String)) As Boolean
            'ハンドルされていない例外を補足する
            '--UIスレッドでの例外を補足
            AddHandler System.Windows.Forms.Application.ThreadException, AddressOf Application_ThreadException
            '--UIスレッド以外での例外を補足
            AddHandler System.AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException

            Return MyBase.OnInitialize(commandLineArgs)
        End Function

        ''' 
        ''' UIスレッドでの例外を補足するイベント
        ''' 
        ''' 
        ''' 
        ''' 
        Private Sub Application_ThreadException(sender As Object, e As System.Threading.ThreadExceptionEventArgs)
            'スプラッシュ画面を閉じる
            CloseSplashScreen()
            '例外処理を行う
            ShowError(e.Exception)
        End Sub
        
        ''' 
        ''' UIスレッド以外での例外を補足するイベント
        ''' 
        ''' 
        ''' 
        ''' 
        Private Sub CurrentDomain_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs)
            '※UIスレッド以外のスレッドなので、UIスレッドの画面操作はできない
            '-----
            ''スプラッシュ画面を閉じる
            'CloseSplashScreen()
            '-----
            '例外処理を行う
            ShowError(DirectCast(e.ExceptionObject, Exception))
        End Sub

        ''' 
        ''' スプラッシュ画面を閉じる
        ''' 
        ''' 
        Private Sub CloseSplashScreen()
            If (My.Application.SplashScreen IsNot Nothing AndAlso My.Application.SplashScreen.IsDisposed <> True) Then
                My.Application.SplashScreen.Close()
            End If
        End Sub

        ''' 
        ''' 例外処理を行う
        ''' 
        ''' 
        ''' 
        Private Sub ShowError(ex As Exception)
            Dim info As New System.Text.StringBuilder
            info.AppendLine(ex.Message)
            info.AppendLine(ex.StackTrace)
            info.AppendLine("------メソッド呼出履歴------")
            For Each frm As StackFrame In New StackTrace(ex, True).GetFrames
                Dim mb As System.Reflection.MethodBase = frm.GetMethod
                info.AppendLine(mb.ReflectedType.FullName & " : " & mb.Name)
            Next

            MessageBox.Show(info.ToString)
        End Sub

    End Class

End Namespace

NET(VB) VBでは0除算例外が出ない

VBでは0除算例外(DivideByZeroException)が出ないなんて
ずっとVBやってたきたけど知らなかったよ。
しばらくC#やってたから忘れたのかナ…

詳しくはコチラhttp://salvw.miscnotes.com/index.php/archives/1282


Dim i1 As Integer = 1
Dim i2 As Integer = 0

結果を格納する変数の型がDoubleなら落ちない。
Dim ret1 As Double = i1 / i2

結果を格納する変数の型がDoubleでないと落ちる。
Dim ret2 As Integer = i1 / i2

・i1とi2がDoubleにキャストされて、除算が行われる。CDbl(i1) / CDbl(i1)
・除算の結果は正の無限大または負の無限大になる。
・結果を格納する変数がDoubleなら落ちない。
・結果を格納する変数がDoubleでないなら、無限大の値を格納しようとしてオーバーフロー例外がスローされる。


Decimal型であれば事前にDouble型に変換されないので、0除算例外が発生する。
Dim d1 As Decimal = 1
Dim d2 As Decimal = 0
Dim ret3 As Double = d1 / d2


除算するときはDecimalにキャストしてからにしよっと。

.Net(VB C#) LINQ
Enumerable.TakeWhile メソッド

Enumerable.TakeWhileメソッド
【C#】
指定された条件を満たされる限り、シーケンスから要素を返した後、残りの要素をスキップします。
public static IEnumerable<TSource> TakeWhile<TSource>
    (this IEnumerable<TSource> source, Func<TSource, bool> predicate)

指定された条件が満たされる限り、シーケンスから要素を返します。要素のインデックスは、述語関数のロジックで使用されます。
public static IEnumerable TakeWhile
    (this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
【VB】
指定された条件を満たされる限り、シーケンスから要素を返した後、残りの要素をスキップします。
<ExtensionAttribute>
Public Shared Function TakeWhile(Of TSource) 
    (source As IEnumerable(Of TSource), predicate As Func(Of TSource, Boolean))
     
指定された条件が満たされる限り、シーケンスから要素を返します。要素のインデックスは、述語関数のロジックで使用されます。
<ExtensionAttribute>
Public Shared Function TakeWhile(Of TSource) 
    (source As IEnumerable(Of TSource), predicate As Func(Of TSource, Integer, Boolean)) 
    As IEnumerable(Of TSource)


簡単に言うと…
シーケンス(配列やコレクション)の先頭から、指定された条件に該当する間その要素を返します。

このメソッドは遅延実行されます。


テストデータです。
【C#】
private class Fruit
{
    public string Name { get; set; }
    public string Rank { get; set; }
    public decimal Price { get; set; }
}

var fruits = new List<Fruit>()
                {
                    new Fruit(){Name = "りんご", Rank = "A" , Price = 1000 },
                    new Fruit(){Name = "みかん", Rank = "A" , Price = 600 },
                    new Fruit(){Name = "ぶどう", Rank = "B" , Price = 1200 },
                    new Fruit(){Name = "りんご", Rank = "B" , Price = 800 },
                    new Fruit(){Name = "みかん", Rank = "A" , Price = 500 }
                };
【VB】
Private Class Fruit
    Public Property Name As String
    Public Property Rank As String
    Public Property Price As Decimal
End Class

Dim fruits = New List(Of Fruit)() From
            {
                New Fruit() With {.Name = "りんご", .Rank = "A", .Price = 1000},
                New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 600},
                New Fruit() With {.Name = "ぶどう", .Rank = "B", .Price = 1200},
                New Fruit() With {.Name = "りんご", .Rank = "B", .Price = 800},
                New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 500}
            }
TakeWhileメソッドを使用して、先頭から値段が1000円以下の条件が満たされる間、その要素を取得します。
つまり、先頭から順に1000円以下かどうか判定し、1000円以下であればその要素を返します。
ぶどうが1200円ですので条件には該当しません。ここで判定はストップします。
ぶどう以降のりんごやみかんは1000円以下ですが無視されます。

【C#】
//先頭から値段が1000円以下の条件が満たされる間、その要素を取得します。
var lst1 = fruits.TakeWhile(itm => itm.Price <= 1000);
//出力
foreach (var itm in lst1)
    Console.WriteLine("名称={0}, ランク={1}, 値段={2}", itm.Name, itm.Rank, itm.Price.ToString("#,##0"));  
//名称=りんご, ランク=A, 値段=1,000
//名称=みかん, ランク=A, 値段=600
【VB】
'先頭から値段が1000円以下の条件が満たされる間、その要素を取得します。
Dim lst1 = fruits.TakeWhile(Function(itm) itm.Price <= 1000)
'出力
For Each itm In lst1
    Console.WriteLine("名称={0}, ランク={1}, 値段={2}", itm.Name, itm.Rank, itm.Price.ToString("#,##0"))
Next
'名称=りんご, ランク=A, 値段=1,000
'名称=みかん, ランク=A, 値段=600
各要素のインデックスを取得できるオーバーロードメソッドもあります。
以下のコードではインデックスが3以下の間、その要素を取得します。
※Takeメソッドは引数に取得する個数を指定します。Take(3)と指定すると先頭から3つの要素を取得します。
【C#】
//インデックスが3以下の条件が満たされる間、その要素を取得します。
var lst1 = fruits.TakeWhile((itm, idx) => idx <= 3);
//出力
foreach (var itm in lst1)
    Console.WriteLine("名称={0}, ランク={1}, 値段={2}", itm.Name, itm.Rank, itm.Price.ToString("#,##0"));  
//名称=りんご, ランク=A, 値段=1,000
//名称=みかん, ランク=A, 値段=600
//名称=ぶどう, ランク=B, 値段=1,200
//名称=りんご, ランク=B, 値段=800
【VB】
'インデックスが3以下の条件が満たされる間、その要素を取得します。
Dim lst1 = fruits.TakeWhile(Function(itm, idx) idx <= 3)
'出力
For Each itm In lst1
    Console.WriteLine("名称={0}, ランク={1}, 値段={2}", itm.Name, itm.Rank, itm.Price.ToString("#,##0"))
Next
'名称=りんご, ランク=A, 値段=1,000
'名称=みかん, ランク=A, 値段=600
'名称=ぶどう, ランク=B, 値段=1,200
'名称=りんご, ランク=B, 値段=800    

※VBの場合、コンパイルオプションで「Option Infer」をONにし、型推論を有効にしてください。

.Net(VB C#) LINQのメソッド一覧