0
votes

Edit: Based on the answer from LoveMeSomeCode, I believe this issue only appears in VB.Net.

I'm trying to revert a class to a previous state by saving the old values of changed properties in a dictionary and setting them via reflection when I need to revert. I'm having a problem where if the old value is Nothing (null) I get a null reference exception when trying to set the property. Here's what I've tried.

Assume a for each loop like this:

For Each pair As KeyValuePair(Of String, Object) In myOldValues
...
Next

Method 1:

CallByName(Me, pair.Key, CallType.Set, pair.Value)

Method 2:

Me.GetType().InvokeMember(pair.Key, Reflection.BindingFlags.SetProperty, Nothing, Me, pair.Value)

Method 3:

Dim propInfo As System.Reflection.PropertyInfo = Me.GetType.GetProperty(pair.Key)
propInfo.SetValue(Me, Convert.ChangeType(pair.Value, propInfo.PropertyType), Nothing)

For each of these methods I get a null reference exception when pair.Value is null. The setter is capable of holding a null value (frequently the property is a string). What am I doing wrong or how can I work around it?

Edit: Each method fails if I pass it null directly as well.

Edit: Here are the stack traces if they help anyone:

Method 1 System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.VisualBasic.CompilerServices.Symbols.Container.InvokeMethod(Method TargetProcedure, Object[] Arguments, Boolean[] CopyBack, BindingFlags Flags) at Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateSet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean OptimisticSet, Boolean RValueBase, CallType CallType) at Microsoft.VisualBasic.CompilerServices.Versioned.CallByName(Object Instance, String MethodName, CallType UseCallType, Object[] Arguments) at myProject.Presenter.CustomerDetailPresenter.RevertCustomer() in myfile:line 378

Method 2 System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object. at myProject.Presenter.CustomerDetailPresenter.set_City(String value) --- End of inner exception stack trace --- at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams) at System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args) at myProject.Presenter.CustomerDetailPresenter.RevertCustomer()

Method 3 System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object. at myProject.Presenter.CustomerDetailPresenter.set_City(String value) --- End of inner exception stack trace --- at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)

at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams) at System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args) at myProject.Presenter.CustomerDetailPresenter.RevertCustomer()

3
Are you sure that PropertyInfo.SetValue is the method throwing the exception?jason
That or one of it's sub methods, it is that line.Bryan Anderson
Check the exception stack trace; you should be able to discern which method is throwing the exception. If you can not figure that out, try calling Convert.ChangeType separately. My suspicion is that method is throwing the exception.jason
Thanks for helping me figure this out. I had tried having the Convert.ChangeType and all of the other sub calls separate. They all worked properly until I called whichever function actually tried to invoke the setter.Bryan Anderson

3 Answers

3
votes

The fact that you are seeing this in the 2nd and 3rd option stack traces

System.NullReferenceException: Object reference not set to an instance of an object. at myProject.Presenter.CustomerDetailPresenter.set_City(String value)

makes me think that there is something in your CustomerDetailPresenter.City property setter that is not handling the null value. What is the implementation of your property setter? Is that any validation or auditing code that could be failing?

Update 03-24-2009: A quick test in VB, and this code works as intended. I tried to capture the scenario you describe.

My test class that has its property set (in part):

Public Class MyObject

    Private mId As Integer
    Private mName As String
    Private mDOB As Date
     .......
     .......
    Public Property Name() As String
        Get
            Return mName
        End Get
        Set(ByVal Value As String)
            mName = Value
        End Set
    End Property

I have created a PropertyState class that will hold the property name, value and type. And the code to set the property dynamically is:

Private Sub SetValues()
        'get object that we are working with
        Dim ty As Type = mObjectInstance.GetType

        'create our property name/value info
        Dim info As New PropertyState
        With info
            .PropName = "Name"
            .OriginalValue = Nothing
            .ValueType = GetType(String)
        End With

        'now use reflection to set value on object
        Dim prop As PropertyInfo = ty.GetProperty("Name", BindingFlags.Instance Or BindingFlags.Public)
        'use Convert.ChangeType to duplicate problem scenario
        Dim newValue = Convert.ChangeType(Nothing, GetType(String))
        'prop.SetValue(mObjectInstance, newValue, BindingFlags.Instance Or BindingFlags.Public, Nothing, Nothing, Globalization.CultureInfo.CurrentUICulture)
        prop.SetValue(mObjectInstance, Convert.ChangeType(info.OriginalValue, info.ValueType), Nothing)

        DisplayValues(CType(mObjectInstance, MyObject))
    End Sub

I used two different overloads of the SetValue method, I have found that not explicity setting the BindingFlags can cause reflection issues at times. However, in this case, both overlaods work fine.

So, I look back to the stack trace you posted in your question:

System.NullReferenceException: Object reference not set to an instance of an object. at myProject.Presenter.CustomerDetailPresenter.set_City(String value)

the fact that the set_City() setter is what is throwing the exception indicates that the method is being found and called successfully. The null(nothing) value is being passed in as requested. So, the bug is not in the reflection but in what is happening as a result of the property setter being called. You probably have already tried this, but setting a break point in the setter or setting the IDE to break on all managed exceptions to see if you can capture the actual cause? Or, is the state of the stored property info what is expected? Name, type and value all in sync?

Hope this helps.

0
votes

In SetValue, Convert.ChangeType calls IConvertible methods on pair.Value, surely they fail when you try to call them on null instance.

Check pair.Value for nullness and pass an explicit null if it is the case.

InvokeMember expects an array as 5th argument. Try:

Params(0) = pair.Value
Me.GetType().InvokeMember(pair.Key, Reflection.BindingFlags.SetProperty, Nothing, Me, Params)
0
votes

Well, this is C# instead of VB.NET, but this seems to work:

private Dictionary<string, object> MemoryValues = new Dictionary<string, object>();

        public void Store()
        {
            foreach (PropertyInfo info in this.GetType().GetProperties())
            {
                if (MemoryValues.ContainsKey(info.Name))
                    MemoryValues[info.Name] = info.GetValue(this, null);
                else
                    MemoryValues.Add(info.Name, info.GetValue(this, null));
            }
        }

        public void Recall()
        {
            foreach (PropertyInfo info in this.GetType().GetProperties())
            {
                info.SetValue(this, MemoryValues[info.Name], null);
            }
        }

so you can set the properties and call Store(), and they will be saved to the dictionary. Then you can change them and call Recall() and they will be restored. It seems to work will null for strings at least. It seems like a good set of things to put in a base class.