【问题标题】:Use reflection to set a property value to Nothing (Null)使用反射将属性值设置为 Nothing (Null)
【发布时间】:2010-10-03 01:12:24
【问题描述】:

编辑:根据 LoveMeSomeCode 的回答,我相信这个问题只出现在 VB.Net 中。

我试图通过将更改的属性的旧值保存在字典中并在需要恢复时通过反射设置它们来将类恢复到以前的状态。我遇到了一个问题,如果旧值是 Nothing (null),我在尝试设置属性时会得到一个空引用异常。这是我尝试过的。

为每个循环假设一个这样的:

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

方法一:

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

方法二:

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

方法三:

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

对于这些方法中的每一个,当 pair.Value 为 null 时,我都会得到一个 null 引用异常。 setter 能够保存一个空值(通常该属性是一个字符串)。我做错了什么或如何解决?

编辑:如果我也直接传递 null,每个方法都会失败。

编辑:如果对任何人有帮助,以下是堆栈跟踪:

方法一 System.NullReferenceException:对象引用未设置为对象的实例。 在 Microsoft.VisualBasic.CompilerServices.Symbols.Container.InvokeMethod(方法 TargetProcedure,Object[] 参数,Boolean[] CopyBack,BindingFlags 标志) 在 Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateSet(对象实例,类型类型,字符串成员名称,对象 [] 参数,字符串 [] 参数名称,类型 [] 类型参数,布尔 OptimisticSet,布尔 RValueBase,CallType CallType) 在 Microsoft.VisualBasic.CompilerServices.Versioned.CallByName(对象实例,字符串方法名,CallType UseCallType,对象 [] 参数) myProject.Presenter.CustomerDetailPresenter.RevertCustomer() in myfile:第 378 行

方法二 System.Reflection.TargetInvocationException:调用的目标已引发异常。 ---> System.NullReferenceException:对象引用未设置为对象的实例。 在 myProject.Presenter.CustomerDetailPresenter.set_City(字符串值) --- 内部异常堆栈跟踪结束 --- 在 System.RuntimeMethodHandle._InvokeMethodFast(对象目标,Object[] 参数,SignatureStruct& sig,MethodAttributes methodAttributes,RuntimeTypeHandle typeOwner) 在 System.RuntimeMethodHandle.InvokeMethodFast(对象目标,对象 [] 参数,签名 sig,MethodAttributes 方法属性,RuntimeTypeHandle typeOwner) 在 System.Reflection.RuntimeMethodInfo.Invoke(Object obj,BindingFlags invokeAttr,Binder binder,Object[] 参数,CultureInfo 文化,布尔型 skipVisibilityChecks) 在 System.Reflection.RuntimeMethodInfo.Invoke(Object obj,BindingFlags invokeAttr,Binder binder,Object[] 参数,CultureInfo 文化) 在 System.RuntimeType.InvokeMember(字符串名称、BindingFlags bindingFlags、Binder binder、Object 目标、Object[] providedArgs、ParameterModifier[] 修饰符、CultureInfo 文化、String[] namedParams) 在 System.Type.InvokeMember(字符串名称,BindingFlags invokeAttr,Binder binder,Object target,Object[] args) 在 myProject.Presenter.CustomerDetailPresenter.RevertCustomer()

方法三 System.Reflection.TargetInvocationException:调用的目标已引发异常。 ---> System.NullReferenceException:对象引用未设置为对象的实例。 在 myProject.Presenter.CustomerDetailPresenter.set_City(字符串值) --- 内部异常堆栈跟踪结束 --- 在 System.RuntimeMethodHandle._InvokeMethodFast(对象目标,Object[] 参数,SignatureStruct& sig,MethodAttributes methodAttributes,RuntimeTypeHandle typeOwner) 在 System.RuntimeMethodHandle.InvokeMethodFast(对象目标,对象 [] 参数,签名 sig,MethodAttributes 方法属性,RuntimeTypeHandle typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfoculture, Boolean skipVisibilityChecks)

在 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] 参数, CultureInfo 文化) 在 System.RuntimeType.InvokeMember(字符串名称、BindingFlags bindingFlags、Binder binder、Object 目标、Object[] providedArgs、ParameterModifier[] 修饰符、CultureInfo 文化、String[] namedParams) 在 System.Type.InvokeMember(字符串名称,BindingFlags invokeAttr,Binder binder,Object target,Object[] args) 在 myProject.Presenter.CustomerDetailPresenter.RevertCustomer()

【问题讨论】:

  • 你确定 PropertyInfo.SetValue 是抛出异常的方法吗?
  • 那个或者它的子方法之一,就是那一行。
  • 检查异常堆栈跟踪;您应该能够辨别哪个方法引发了异常。如果您无法弄清楚,请尝试单独调用 Convert.ChangeType。我怀疑该方法正在引发异常。
  • 感谢您帮助我解决这个问题。我曾尝试将 Convert.ChangeType 和所有其他子调用分开。在我调用实际尝试调用 setter 的任何函数之前,它们都正常工作。

标签: .net vb.net reflection null


【解决方案1】:

您在第二个和第三个选项堆栈跟踪中看到了这一点

System.NullReferenceException: 对象 引用未设置为 目的。在 myProject.Presenter.CustomerDetailPresenter.set_City(字符串 值)

让我觉得您的 CustomerDetailPresenter.City 属性设置器中有一些东西没有处理空值。您的属性设置器的实现是什么?是否有任何可能失败的验证或审计代码?

2009 年 3 月 24 日更新: VB 中的快速测试,此代码按预期工作。我试图捕捉你描述的场景。

我的测试类有它的属性集(部分):

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

我创建了一个 PropertyState 类,它将保存属性名称、值和类型。而动态设置属性的代码是:

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

我使用了两种不同的 SetValue 方法重载,我发现不明确设置 BindingFlags 有时会导致反射问题。但是,在这种情况下,两个重载都可以正常工作。

所以,我回顾一下您在问题中发布的堆栈跟踪:

System.NullReferenceException: 对象 引用未设置为 目的。在 myProject.Presenter.CustomerDetailPresenter.set_City(字符串 值)

set_City() setter 是引发异常的原因,这表明该方法已被成功找到并调用。正在按请求传入 null(nothing) 值。因此,错误不在反射中,而是在调用属性设置器时发生的事情。您可能已经尝试过,但是在设置器中设置断点或设置 IDE 以中断所有托管异常以查看是否可以捕获实际原因?或者,存储的属性信息的状态是预期的吗?名称、类型和值是否同步?

希望这会有所帮助。

【讨论】:

  • 不,这是一个标准的字符串属性。私有 myCity 作为字符串,Get = {Return myCity},Set={myCity=Value}。我什至删掉了 NotifyPropertyChanged 代码,看看是不是这样。
【解决方案2】:

SetValue 中,Convert.ChangeTypepair.Value 上调用IConvertible 方法,当您尝试在null 实例上调用它们时,它们肯定会失败。

检查pair.Value 是否为空,如果是,则传递明确的null

InvokeMember 需要一个数组作为第 5 个参数。试试:

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

【讨论】:

  • 如果我也直接传递 null,每个方法都会失败。
  • 当我用 null 调用它时,Convert.ChangeType 也不会失败。
【解决方案3】:

嗯,这是 C# 而不是 VB.NET,但这似乎可行:

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);
            }
        }

这样您就可以设置属性并调用 Store(),它们将被保存到字典中。然后您可以更改它们并调用 Recall(),它们将被恢复。它似乎至少对字符串有效。放在基类中似乎是一组很好的东西。

【讨论】:

  • 会不会有 Vb.Net/C# 的区别?我正在做 VB 等效项 propInfo.SetValue(Me, pair.Value, Nothing),并得到一个 NullReferenceException。即使只是 propInfo.SetValue(Me, Nothing, Nothing) 也会给我 NullReferenceException。
  • 是的,一定有区别,因为我可以在 C# 中运行它: PropertyInfo pInfo = mc.GetType().GetProperties()[1]; pInfo.SetValue(mc, null, null);也不例外(mc 是我的类的一个实例,属性是一个字符串)
  • 皮塔饼。看起来我需要添加一个 VB.Net 标记。感谢您的帮助。
  • 我怀疑 VB.NET 和 C# 之间是否存在差异。这两种方法可能在 mscorlib 中调用相同的 .NET 方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多