I've read about Lazy Initialization but practically I don't understand how to initialize the objects inside.
This function returns a LazyList
which is a kind of custom implementation of a Lazy type
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
When I try to read a property of any item of the LazyList I get a NullReferenceException
exception, I suppose that this is because the object is not initialized? 'cause note that the lazylist item count is "2", and I'm totally sure that the property which I try to read can't be null.
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
If I try to reproduce what I do in the code above (try to read the properties) but inside the function then all goes as expected without any nullreference exception... like in this example:
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: The imports are from dnlib library.
UPDATE:
I would like to explain the problem with more and better examples.
Two thinngs before continue explaining:
I ensured that in all examples the TypeName
parameter exists and iis found, I've ensured that the returned collection of objects by the function never is empty, it has an Collection.Count of 2 as I've explained at the start of the question, so any of that is the problem.
Well, the next function returns a List of objects, those objects come with a property named HasBody
but this property ALWAYS is empty (throwing a NullReference exception) when it should not be empty, the value should be True for the both items contained in the collection.
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
By the other hand, If I perform a ridiculous modification in the function (see the changes related to the tmp
object) the function returns a list of objects where the HasBody
property is initialized, is not empty, and the 2 items contained in the returned list a HasBody
property with a value of 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
So where is the problem here and how to fix it?, maybe that is a temporal solution for this situation but the methoddef
object that returns the function contains a lot of properties and I need to access to more properties than HasBody
in the future so I really can't be "assigning" each property to the tmp
object in the function to solve that in a ugly way...
and the code used for loop through the returned List in both cases whas this:
Note: remember that, with the first function I can't parse the method.hasbody
property and neither the method.body.instructions
property, both throw a NullReference exception.
But with the modified function I can parse the method.hasbody
property only if I've assigned it to the tmp
variable inside the function before returning the List, and the same for the method.body.instructions
property if did the same.
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
I think this is the first problem:
Public Function GetMethods(ByVal Assembly As String,
ByVal TypeName As String) 'As List(Of MethodDef)
Specify the return type and it may work: As LazyList(Of MethodDef)
This will also happen when the TypeName
is not found in the Assembly. In that case, your LazyList will still be Nothing and result in the NRE. You should check the return - I dont think it has to do with the LazyList (yet):
Dim methods As LazyList(Of MethodDef) = _
GetMethods("C:\WindowsApplication.exe", "Main")
If methods IsNot Nothing Then
' iterate
TestBed:
Dim methods As LazyList(Of MethodDef) = Nothing
In a button click:
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
Test handler is essentially what you have &= As LazyList(Of MethodDef)
. Output:
.ctor has a body
testfunction has a body
Foo has a body
Bar has a body
ziggy has a body
The bigger problem may come next: ... I don't understand how to initialize the objects inside
What you stored in the LazyList are these MethodDef
objects -- they are already instantiated. TypeDef.Methods
actually returns an IList of MethodDef
, the only reason a LazyList is involved is because your code stores them that way.
If you mean to invoke the method(s) they describe, I am pretty sure you cant do it using a MethodDef
as a starting point. They are just metadata describing the characteristics of the method. You already know the TypeName
they came from so to invoke one of the methods, you'd have to have an instance of that Type.
Its unknown what you are trying to do, but a LazyList seems unneeded for a collection of metadata.
The definition in Object Browser for Methods
is IList
, as the source also shows:
public ThreadSafe.IList<MethodDef> Methods
Internally, it uses a LazyList. This is probably so it can create and save a list of Methods for a Type but not bother loading the metadata for them unless and until it is actually asked something about the Methods list. Then they are initialized.
This works equally well:
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
You can get too clever and try to just do: Return t.Methods
and get a cast error about LastList cast to List. The set of method objects you get back is already instanced though, so there might be something else messed up if you are getting NREs.
This solves the problem:
Using ass As ModuleDefMD = ModuleDefMD.Load(Assembly)
The List of Method defs are internally a LazyList and wont be instanced until a Property or Method on them is called. To instance them, they (apparently) need access to the thing that created them. So reference something innocuous so you can dispose of 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
This is more or less what you Tmp object does. You call it ugly, but since the source is a LazyList, it is either that or work out something with the modDef
reference.