【问题标题】:How to initialize a Lazy list?如何初始化惰性列表?
【发布时间】:2014-08-02 19:54:30
【问题描述】:

我读过Lazy Initialization,但实际上我不明白如何初始化里面的对象。

这个函数返回一个LazyList,它是Lazy类型的一种自定义实现

Public Function GetMethods(ByVal Assembly As String,
                           ByVal TypeName As String) 'As List(Of MethodDef) ' As MethodDef()

    Dim methods As LazyList(Of MethodDef) = Nothing

    Using ass As ModuleDefMD = ModuleDefMD.Load(Assembly)

        For Each t As TypeDef In ass.GetTypes

            If t.HasMethods AndAlso t.Name.String.Equals(TypeName, StringComparison.OrdinalIgnoreCase) Then

                methods = t.Methods

                '  MsgBox(t.Methods.GetType.Name) ' Result: LazyList'1
                Exit For

            End If

        Next t

        Return methods

    End Using

End Function

当我尝试读取 LazyList 的任何项目的属性时,我得到一个 NullReferenceException 异常,我想这是因为对象没有初始化?因为请注意,lazylist 项目计数为“2”,我完全确定我尝试读取的属性不能为空。

Imports dnlib.DotNet
Imports dnlib.DotNet.Emit
Imports dnlib.Utils

    Dim methods As LazyList(Of MethodDef) = GetMethods("C:\WindowsApplication.exe", "Main")

    MsgBox(methods.IsInitialized(0)) ' Result: False
    MsgBox(methods.Count) ' Result: 2

    For Each method As MethodDef In methods

        ' NullReferenceException exception here:
        MsgBox(method.Name)

        ' NullReferenceException exception here too (reading m.hasbody):
        If method.HasBody Then
            Dim sb As New System.Text.StringBuilder
            With sb
                .AppendLine(String.Format("Method Instructions: {0}", Environment.NewLine &
                                          String.Join(Environment.NewLine, method.Body.Instructions)))
            End With
            Debug.WriteLine(sb.ToString)
        End If

    Next method

如果我尝试重现我在上面的代码中所做的事情(尝试读取属性),但在函数内部,那么一切都按预期进行,没有任何空引用异常......就像在这个例子中一样:

public function GetMethods (...)

' deleted code...

        If t.HasMethods AndAlso t.Name.String.Equals(TypeName, StringComparison.OrdinalIgnoreCase) Then

           methods = t.Methods
            For Each m In methods
                MsgBox(m.Name) ' no exceptions
                MsgBox(m.HasBody) ' no exceptions
            Next

       End If

' deleted code...

end function

PS:导入来自dnlib 库。

更新:

我想用更多更好的例子来解释这个问题。

在继续解释之前有两件事:

我确保在所有示例中都存在 TypeName 参数并且找到了 iis,我确保函数返回的对象集合永远不会为空,它的 Collection.Count 为 2 正如我在问题开始时所解释的,所以任何一个都是问题。


好吧,下一个函数返回一个对象列表,这些对象带有一个名为 HasBody 的属性,但此属性 ALWAYS 为空(抛出 NullReference 异常)当它不应该为空时,集合中包含的两个项目的值都应该是True

Public Function GetMethods(ByVal Assembly As String,
                           ByVal TypeName As String) As List(Of MethodDef)

    Dim methods As List(Of MethodDef) = Nothing

    Using ass As ModuleDefMD = ModuleDefMD.Load(Assembly)

        For Each t As TypeDef In ass.GetTypes

            If t.HasMethods AndAlso t.Name.String.Equals(TypeName, StringComparison.OrdinalIgnoreCase) Then

                methods = t.Methods.ToList
                Exit For

            End If

        Next t

        Return methods

    End Using

End Function

另一方面,如果我在函数中执行了一个荒谬的修改(请参阅与 tmp 对象相关的更改),该函数将返回一个对象列表,其中 HasBody 属性已初始化,不为空,并且返回列表中包含的 2 个项目是一个 HasBody 属性,其值为 True

Public Function GetMethods(ByVal Assembly As String,
                           ByVal TypeName As String) As List(Of MethodDef)

    Dim methods As List(Of MethodDef) = Nothing

    Dim tmp As Object

    Using ass As ModuleDefMD = ModuleDefMD.Load(Assembly)

        For Each t As TypeDef In ass.GetTypes

            If t.HasMethods AndAlso t.Name.String.Equals(TypeName, StringComparison.OrdinalIgnoreCase) Then

                methods = t.Methods.ToList
                For Each m In methods
                    tmp = m.HasBody
                Next

                Exit For

            End If

        Next t

        Return methods

    End Using

End Function

那么问题出在哪里以及如何解决?,也许这是针对这种情况的临时解决方案,但返回函数的 methoddef 对象包含很多属性,我需要访问比 @ 更多的属性987654337@ 在未来,所以我真的不能将每个属性“分配”给函数中的 tmp 对象以丑陋的方式解决这个问题......

在这两种情况下,用于循环返回列表的代码是这样的:

注意:请记住,使用第一个函数我无法解析 method.hasbody 属性和 method.body.instructions 属性,两者都会引发 NullReference 异常。

但是使用修改后的函数,我可以解析 method.hasbody 属性,前提是我在返回列表之前将其分配给函数内部的 tmp 变量,如果执行相同操作,method.body.instructions 属性也是如此.

    Dim methods As List(Of MethodDef) =
        GetMethods("C:\WindowsApplication.exe", "Main")

    For Each method As MethodDef In methods

        MsgBox(method.Name)

        If method.HasBody Then
            Dim sb As New System.Text.StringBuilder
            With sb
                .AppendLine(String.Format("Method Instructions: {0}", Environment.NewLine &
                                          String.Join(Environment.NewLine, method.Body.Instructions)))
            End With
            MsgBox(sb.ToString)
        End If

    Next method

【问题讨论】:

  • 遇到异常时哪个对象为空?
  • @usr "HasBody" 和 "Body.Instructions" 属性,但我认为确实有很多我没有验证的属性是否为空(当它不应该为空时)。如果可以的话,请查看我的问题更新,感谢您的评论。

标签: .net vb.net lazy-evaluation lazy-initialization dnlib


【解决方案1】:

我认为这是第一个问题:

Public Function GetMethods(ByVal Assembly As String,
                       ByVal TypeName As String) 'As List(Of MethodDef) 

指定返回类型,它可能会起作用:As LazyList(Of MethodDef)

当在程序集中找不到TypeName 时也会发生这种情况。在这种情况下,您的 LazyList 仍将是 Nothing 并导致 NRE。你应该检查返回 - 我认为它与 LazyList 无关(还):

Dim methods As LazyList(Of MethodDef) = _
     GetMethods("C:\WindowsApplication.exe", "Main")

If methods IsNot Nothing Then
  ' iterate

测试床:

Dim methods As LazyList(Of MethodDef) = Nothing

在按钮中单击:

methods = Test_Handler("Test2")

For Each meth As MethodDef In methods
    If meth.HasBody Then
        Console.WriteLine("{0} has a body", meth.Name)
    Else
        Console.WriteLine("{0} has NO body", meth.Name)
    End If
Next

测试处理程序本质上就是您所拥有的 &= As LazyList(Of MethodDef)。输出:

.ctor has a body
testfunction has a body
Foo has a body
Bar has a body
ziggy has a body

接下来可能会出现更大的问题:...我不明白如何初始化里面的对象

您在 LazyList 中存储的是这些 MethodDef 对象——它们已经被实例化了。 TypeDef.Methods 实际上返回 MethodDef 的 IList,涉及 LazyList 的唯一原因是因为您的代码以这种方式存储它们。

如果您打算调用他们描述的方法,我很确定您不能使用 MethodDef 作为起点来执行此操作。它们只是描述方法特征的元数据。您已经知道它们来自 TypeName,因此要调用其中一种方法,您必须拥有该类型的实例。

它未知什么您正在尝试做什么,但是对于元数据的集合似乎不需要 LazyList。


对象浏览器中Methods 的定义是IList,源代码也显示:

public ThreadSafe.IList<MethodDef> Methods

在内部,it 使用 LazyList。这可能是因为它可以创建和保存一个类型的方法列表,但不会为它们加载元数据,除非并且直到它实际上被询问有关方法列表的某些内容。 然后它们被初始化。

这同样适用:

Private Function Test_Handler(typeName As String) As List(Of MethodDef)

    Dim modDef As ModuleDefMD = ModuleDefMD.Load("C:\Temp\ConsoleApplication1.exe")
    Dim methods As New List(Of MethodDef)

    For Each t As TypeDef In modDef.GetTypes

        ' stupid way to drill, but will work for demo purposes
        If t.Name.Equals(typeName) Then

            'methods = t.Methods
            methods.AddRange(t.Methods.ToArray)

            Exit For
        End If

    Next

    Return methods

End Function

你可能太聪明了,试着做:Return t.Methods,然后得到一个关于 LastList 转换为 List 的转换错误。但是,您返回的方法对象集已经实例化,因此如果您获得 NRE,可能会出现其他问题。


这样就解决了问题:

Using ass As ModuleDefMD = ModuleDefMD.Load(Assembly)

方法定义列表在内部是一个 LazyList,并且在调用它们的属性或方法之前不会被实例化。为了实例化它们,它们(显然)需要访问创建它们的东西。所以参考一些无害的东西,这样你就可以处理ModuleDefMD

Dim methods As New List(Of MethodDef)
Dim b As Boolean

Using modDef As ModuleDefMD = ModuleDefMD.Load("C:\Temp\ConsoleApplication1.exe")
    For Each t As TypeDef In modDef.GetTypes

        ' stupid way to drill, but will work for demo purposes
        If t.Name.Equals(typeName) Then

            For Each m As MethodDef In t.Methods
                b = m.HasBody

                methods.Add(m)

            Next
            Exit For
        End If

    Next
End Using
Return methods

这或多或少是您的 Tmp 对象所做的。你称它为丑陋的,但由于源是一个 LazyList,它要么就是这样,要么使用 modDef 参考来解决问题。

【讨论】:

  • 将返回类型设置为 'LazyList(Of MethodDef)' 并没有解决问题。您还说,涉及 Lazy 的唯一原因是因为我,但不是,我真的不想使用 Lazy,但是如果我在函数内部时获得返回的数据类型,那就是上面代码中看到的 LazyList (t.Methods.GetType.Name)。评论都没有解决问题,请查看我的问题更新,我已经发布了有助于更好地理解问题的代码,感谢您的回答
  • 两次编辑...您不能将 dnLib 分配给您的列表变量,否则您需要一个 LazyList 并且可能需要一些对辅助函数的调用。
  • 如果我只是尝试使用methods.AddRange(t.Methods.ToArray) 并返回它不起作用的列表,另一方面,您提供的功能有效,是的,但不管@ 987654344@,与你的函数和我的函数唯一重要的区别是 IDisposable ModuleDefMD 对象的使用,我将它放在我的函数中,似乎它与返回的列表有某种关联,我真的不知道为什么!但就是这样,如果我不处理ModuleDefMD(就像在你的函数中一样),我可以在返回列表后读取所有属性。
  • 我只能说谢谢你一直帮助别人,包括一些奇怪的问题,包括像这次这样的第 3 方地下图书馆,非常感谢。
  • 啊!我并没有真正注意到 Using 块。那些模块 def 对象必须依赖它来初始化(我在使用 ToList 时遇到过强制转换问题)。看起来很奇怪,因为 ModuleDefMD 超出了范围。尽管使用 TMP 变量引用它们也实例化了它们,但我假设 ToArray 也会发生同样的事情,但显然只有当你真正使用它时才会发生这种情况。顺便说一句,LIB 唯一真正令人担忧的是缺乏文档和智能帮助——只有这些例子;但到目前为止,我还看不到反射不能做什么。
猜你喜欢
  • 2020-10-10
  • 2021-11-21
  • 1970-01-01
  • 1970-01-01
  • 2012-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多