views:

4317

answers:

3

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()

A: 

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)
Quassnoi
Each method fails if I pass it null directly as well.
Bryan Anderson
Also Convert.ChangeType does not fail when I called it with null.
Bryan Anderson
A: 

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.

LoveMeSomeCode
Could there be a Vb.Net/C# difference? I'm doing the VB equivalent, propInfo.SetValue(Me, pair.Value, Nothing), and getting a NullReferenceException. Even just propInfo.SetValue(Me, Nothing, Nothing) gives me the NullReferenceException.
Bryan Anderson
Yeah, there must be a difference, because I can run this in C#:PropertyInfo pInfo = mc.GetType().GetProperties()[1];pInfo.SetValue(mc, null, null);and no exception (mc is an instance of my class and the property is a string)
LoveMeSomeCode
PITA. Looks like I'll need to add a VB.Net tag. Thanks for the help.
Bryan Anderson
I'm skeptical that there is a difference between VB.NET and C#. The two methods probably call the same .NET method in mscorlib.
Jason
+2  A: 

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.

TheZenker
Nope, it's a standard String property. Private myCity As String, Get = {Return myCity}, Set={myCity=Value}. I even trimmed out the NotifyPropertyChanged code to see if that was it.
Bryan Anderson