none
 LINQ to SQL C# ⇒VB に変換 RRS feed

  • 質問

  • よろしくお願いします。

    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 c

            Dim 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:29

回答

  • わからないエラーや警告が出た場合、検索するとずばりの答えが出てくることが多いので検索することをお勧めします。

    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日 6:25
  • 最初の話をシンプルにすると次のようになりますね。

    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日 12:05

すべての返信

  • こんにちは、おのでらです。

    たぶん Linq の Count() メソッドと List(Of T) の Count プロパティが同名であるためにエラーになってしまっているんですね。

    苦肉の策として以下のように修正するしかないような気がします。

    CType(orders, IEnumerable(Of [orders の要素の型])).Count(Function(o) o.CustomerID = c.CustomerID))

    (ほかにいい方法がありそうな気がするけど…)


    おのでら (http://sorceryforce.com/)
    2011年9月22日 3:50
  • おのでら様。返信、ありがとうございます。

    さっそく、以下のように変更して試したところ、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」です。

    よろしくお願いします。

    2011年9月22日 5:15
  • わからないエラーや警告が出た場合、検索するとずばりの答えが出てくることが多いので検索することをお勧めします。

    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日 6:25
  • おのでら様。回答ありがとうございます。

    無事に警告がなくなり、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 のメンバーを認識し、エラーなく実行されるのかご存知でしたら教えていただきませんか?

    2011年9月22日 8:37
  • > 疑問に思ったのですが、なぜC#では、警告されないのでしょうか?

    Error ID: BC42324VB コンパイラ独自の警告です。C# と VB では言語仕様が全く違うので、C# では同様の処理で警告にならなくても VB では警告になるのだと思います。


    ひらぽん http://d.hatena.ne.jp/hilapon/

    2011年9月22日 9:12
  • > 疑問に思ったのですが、なぜC#では、警告されないのでしょうか?

    Error ID: BC42324VB コンパイラ独自の警告です。C# と VB では言語仕様が全く違うので、C# では同様の処理で警告にならなくても VB では警告になるのだと思います。


    ひらぽん http://d.hatena.ne.jp/hilapon/

    ひらぽん様。回答ありがとうございます。

     

    2011年9月22日 11:57
  • 最初の話をシンプルにすると次のようになりますね。

    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日 12:05
  • TH01様。回答ありがとうございます。

    System.Linq.Enumerable.Count(orders, Function(o) o.CustomerID = c.CustomerID)

    でも正常に実行できました。

     

     BC42324 の件ですが、C#でもそのような問題が発生するのなら、警告も統一してもらいたいと思いました。

    皆様のおかげで、今回、無事に解決できました。また、わからないことがあれば、その時は、よろしくお願いします。

    このたびは、ありがとうございました。

    2011年9月22日 14:56