トップ回答者
LINQ to SQL C# ⇒VB に変換

質問
-
よろしくお願いします。
C# で記述したLINQ to SQL を VB に変換しようと思っていますが、一部エラーになってしまいます。
DataClasses.dbmlは、設定済みで、C#では、正常に実行されます。
db = new DataClassesDataContext();
db.DeferredLoadingEnabled = false;
var customers = from c in db.Customers
where c.City == "London"
select c;var orders = (from o in db.Orders
where o.Customers.City == "London"
select o).ToList();
StringBuilder sb = new StringBuilder();
foreach (var c in customers)
{
sb.AppendFormat("<b>Customer ID: {0}</b><br/>", c.CustomerID);// Run a Count(*) query instead of SELECT *
sb.AppendFormat("Orders (using c.Orders.Count) : {0}<br/>", c.Orders.Count());
sb.AppendFormat("Orders (using orders.Count(...)) : {0}<hr/>", orders.Count(o => o.CustomerID == c.CustomerID));
foreach (var o in orders)
{
if (o.CustomerID == c.CustomerID)
sb.AppendFormat("{0}<br/>", o.OrderID);
}
}Results.Text = sb.ToString();
ここまでは、変換してみましたが、orders.Countのところで、「エラー 1 'Public ReadOnly Property Count As Integer' には引数がないため、戻り値の型をインデックス化できません。」のエラーになってしまいます。
Dim db As New DataClassesDataContext
db.DeferredLoadingEnabled = False
Dim customers = From c In db.Customers
Where c.City = "London"
Select cDim orders = (From o In db.Orders
Where o.Customers.City = "London"
Select o).ToList()Dim sb As StringBuilder = New StringBuilder()
For Each c In customers
sb.AppendFormat("<b>Customer ID: {0}</b><br/>", c.CustomerID)
sb.AppendFormat("Orders (using c.Orders.Count) : {0}<br/>", c.Orders.Count())
sb.AppendFormat("Orders (using orders.Count(...)) : {0}<hr/>", orders.Count(Function(o) o.CustomerID = c.CustomerID))For Each o In orders
If o.CustomerID = c.CustomerID Then
sb.AppendFormat("{0}<br/>", o.OrderID)
End If
Next
Next
Results.Text = sb.ToString()- 編集済み プログラム初心者です 2011年9月22日 3:36
回答
-
わからないエラーや警告が出た場合、検索するとずばりの答えが出てくることが多いので検索することをお勧めします。
http://msdn.microsoft.com/ja-jp/library/bb763133.aspx
ここでは Count() メソッド内で「Function(o)」(ラムダ式)を定義しており、その処理内容に「c.CustomerID」を使用しています。
c は For Each の繰り返し変数であり、Function(o) (ラムダ式)は遅延評価(遅延実行)される可能性があるため、Function(o) を呼び出した時点で c の内容が変わってしまうことを示唆しています。
今回のコードではそのような予期しない処理が発生することはないと思いますが、上記リンク先のようなコードを書いてしまいますと原因が分かりにくいバグとなってしまう可能性がありますので注意が必要です。
今回の警告を解除するには以下のようにコードを修正します。(検証していないので正しいかどうかは確認願いします。)
【修正前】
sb.AppendFormat("Orders (using orders.Count(...)) : {0}<hr/>", CType(orders, IEnumerable(Of Orders)).Count(Function(o) o.CustomerID = c.CustomerID))
【修正後】
Dim c2 = c
sb.AppendFormat("Orders (using orders.Count(...)) : {0}<hr/>", CType(orders, IEnumerable(Of Orders)).Count(Function(o) o.CustomerID = c2.CustomerID))' 一度ローカル変数にコピーすることにより Function(o) が破棄されるまでその値を一時的に保持することができます。
おのでら (http://sorceryforce.com/)- 回答としてマーク プログラム初心者です 2011年9月22日 11:52
-
最初の話をシンプルにすると次のようになりますね。
C#
using System.Linq; var x = new List<int>(); var y = x.Count(o => true); // 正常
VB.NET
Imports System.Linq Dim x = New List(Of Integer) Dim y = x.Count(Function(o) True) 'エラー BC32016
おのでらさんが書かれた件、同名の拡張メソッドを自作して試してみましたところ、同じ結果になりました。
Public Interface ITestInterface End Interface Public Class TestClass Implements ITestInterface Public ReadOnly Property ReturnInteger() As Integer Get Return 10 End Get End Property End Class Imports System.Runtime.CompilerServices Module Module1 <Extension()> _ Public Function ReturnInteger(ByVal target As ITestInterface) As Integer Return 20 End Function <Extension()> _ Public Function ReturnInteger(ByVal target As ITestInterface, ByVal param As Integer) As Integer Return 30 End Function <Extension()> _ Public Function ReturnInteger(ByVal target As TestClass, ByVal param1 As Integer, ByVal param2 As Integer) As Integer Return 40 End Function End Module Public Class Form1 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim x = New TestClass() Dim y1 = x.ReturnInteger 'y1 … 10 Dim y2 = x.ReturnInteger() 'y2 は 20 ではなく 10 Dim y3 = x.ReturnInteger(1) 'エラー BC32016 Dim y4 = x.ReturnInteger(1, 1) 'エラー BC32016 End Sub End Class
この話、下記サイトの「2. Be wary of extension methods」に書いてますね。
MSDN Blogs > The Visual Basic Team
Extension Methods Best Practices (Extension Methods Part 6)
http://blogs.msdn.com/b/vbteam/archive/2007/03/10/extension-methods-best-practices-extension-methods-part-6.aspxただ、インストール先にある VB の Specifications を見てもこの件は書かれていないようでした。
本来は拡張メソッドが使われるべき(使ってほしい)という視点でとらえると、以下のコードもいいかなと思いました。CType(orders, IEnumerable(Of Orders)).Count(Function(o) o.CustomerID = c.CustomerID)
↓
System.Linq.Enumerable.Count(orders, Function(o) o.CustomerID = c.CustomerID)それと BC42324 の補足ですが、
C# では警告されないというだけで、C# ではそのような問題が発生しないというわけではないです。
逆に、今回のコードもそうですが、使い方によっては問題にはなりませんので、常に警告されちゃうのはどうかなと私は思いました。- 回答としてマーク プログラム初心者です 2011年9月22日 14:57
- 編集済み TH01 2011年10月26日 12:28 コメントの誤字の訂正
すべての返信
-
こんにちは、おのでらです。
たぶん Linq の Count() メソッドと List(Of T) の Count プロパティが同名であるためにエラーになってしまっているんですね。
苦肉の策として以下のように修正するしかないような気がします。
CType(orders, IEnumerable(Of [orders の要素の型])).Count(Function(o) o.CustomerID = c.CustomerID))
(ほかにいい方法がありそうな気がするけど…)
おのでら (http://sorceryforce.com/)- 回答としてマーク プログラム初心者です 2011年9月22日 11:52
- 回答としてマークされていない プログラム初心者です 2011年9月22日 11:53
-
おのでら様。返信、ありがとうございます。
さっそく、以下のように変更して試したところ、C#と同じ実行結果が表示されるようになりましたが、新たに警告がでるようになってしまいました。
sb.AppendFormat("Orders (using orders.Count(...)) : {0}<hr/>",
CType(orders, IEnumerable(Of Orders)).Count(Function(o) o.CustomerID = c.CustomerID))c のところで
警告 1 ラムダ式内で繰り返し変数を使用すると、予期しない結果が発生する可能性があります。代わりに、ループ内にローカル変数を作成して繰り返し変数の値を割り当ててください。
データベースは、SQLServer学習用の「Northwind」です。
よろしくお願いします。
-
わからないエラーや警告が出た場合、検索するとずばりの答えが出てくることが多いので検索することをお勧めします。
http://msdn.microsoft.com/ja-jp/library/bb763133.aspx
ここでは Count() メソッド内で「Function(o)」(ラムダ式)を定義しており、その処理内容に「c.CustomerID」を使用しています。
c は For Each の繰り返し変数であり、Function(o) (ラムダ式)は遅延評価(遅延実行)される可能性があるため、Function(o) を呼び出した時点で c の内容が変わってしまうことを示唆しています。
今回のコードではそのような予期しない処理が発生することはないと思いますが、上記リンク先のようなコードを書いてしまいますと原因が分かりにくいバグとなってしまう可能性がありますので注意が必要です。
今回の警告を解除するには以下のようにコードを修正します。(検証していないので正しいかどうかは確認願いします。)
【修正前】
sb.AppendFormat("Orders (using orders.Count(...)) : {0}<hr/>", CType(orders, IEnumerable(Of Orders)).Count(Function(o) o.CustomerID = c.CustomerID))
【修正後】
Dim c2 = c
sb.AppendFormat("Orders (using orders.Count(...)) : {0}<hr/>", CType(orders, IEnumerable(Of Orders)).Count(Function(o) o.CustomerID = c2.CustomerID))' 一度ローカル変数にコピーすることにより Function(o) が破棄されるまでその値を一時的に保持することができます。
おのでら (http://sorceryforce.com/)- 回答としてマーク プログラム初心者です 2011年9月22日 11:52
-
おのでら様。回答ありがとうございます。
無事に警告がなくなり、C#と同じ実行結果が表示されるようになりました。
疑問に思ったのですが、なぜC#では、警告されないのでしょうか?
また、VBでは、以下の
Public ReadOnly Property Count As Integer
System.Collections.Generic.List(Of T) のメンバーPublic Shared Function Count(Of TSource)(ByVal source As System.Collections.Generic.IEnumerable(Of TSource), ByVal predicate As System.Func(Of TSource, Boolean)) As Integer
System.Linq.Enumerable のメンバー2つが、C#では、System.Linq.Enumerable のメンバーを認識し、エラーなく実行されるのかご存知でしたら教えていただきませんか?
-
> 疑問に思ったのですが、なぜC#では、警告されないのでしょうか?
Error ID: BC42324 は VB コンパイラ独自の警告です。C# と VB では言語仕様が全く違うので、C# では同様の処理で警告にならなくても VB では警告になるのだと思います。
ひらぽん http://d.hatena.ne.jp/hilapon/
ひらぽん様。回答ありがとうございます。
-
最初の話をシンプルにすると次のようになりますね。
C#
using System.Linq; var x = new List<int>(); var y = x.Count(o => true); // 正常
VB.NET
Imports System.Linq Dim x = New List(Of Integer) Dim y = x.Count(Function(o) True) 'エラー BC32016
おのでらさんが書かれた件、同名の拡張メソッドを自作して試してみましたところ、同じ結果になりました。
Public Interface ITestInterface End Interface Public Class TestClass Implements ITestInterface Public ReadOnly Property ReturnInteger() As Integer Get Return 10 End Get End Property End Class Imports System.Runtime.CompilerServices Module Module1 <Extension()> _ Public Function ReturnInteger(ByVal target As ITestInterface) As Integer Return 20 End Function <Extension()> _ Public Function ReturnInteger(ByVal target As ITestInterface, ByVal param As Integer) As Integer Return 30 End Function <Extension()> _ Public Function ReturnInteger(ByVal target As TestClass, ByVal param1 As Integer, ByVal param2 As Integer) As Integer Return 40 End Function End Module Public Class Form1 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim x = New TestClass() Dim y1 = x.ReturnInteger 'y1 … 10 Dim y2 = x.ReturnInteger() 'y2 は 20 ではなく 10 Dim y3 = x.ReturnInteger(1) 'エラー BC32016 Dim y4 = x.ReturnInteger(1, 1) 'エラー BC32016 End Sub End Class
この話、下記サイトの「2. Be wary of extension methods」に書いてますね。
MSDN Blogs > The Visual Basic Team
Extension Methods Best Practices (Extension Methods Part 6)
http://blogs.msdn.com/b/vbteam/archive/2007/03/10/extension-methods-best-practices-extension-methods-part-6.aspxただ、インストール先にある VB の Specifications を見てもこの件は書かれていないようでした。
本来は拡張メソッドが使われるべき(使ってほしい)という視点でとらえると、以下のコードもいいかなと思いました。CType(orders, IEnumerable(Of Orders)).Count(Function(o) o.CustomerID = c.CustomerID)
↓
System.Linq.Enumerable.Count(orders, Function(o) o.CustomerID = c.CustomerID)それと BC42324 の補足ですが、
C# では警告されないというだけで、C# ではそのような問題が発生しないというわけではないです。
逆に、今回のコードもそうですが、使い方によっては問題にはなりませんので、常に警告されちゃうのはどうかなと私は思いました。- 回答としてマーク プログラム初心者です 2011年9月22日 14:57
- 編集済み TH01 2011年10月26日 12:28 コメントの誤字の訂正