VB.NET反射对象属性调用ToString格式化时抛出异常的问题求助
我可以直接把Int32类型的变量I1格式化为货币格式,输出I1 = $31,742,但通过反射获取类的Int32属性信息、属性值和格式字符串后,无法完成格式化。在GetPropertyString方法中,调试显示Value的类型确实是Int32,但执行语句Dim Value2 As String = Value.ToString(HandlerFormatProvider)时抛出以下异常:
System.InvalidCastException HResult=0x80004002 Message=Conversion from string "C0" to type 'Integer' is not valid. Source=Microsoft.VisualBasic StackTrace: at Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value) at PropertyInfoType.Module1.GetPropertyString[T](T TObject) in C:\Data\VBS.NET\PropertyInfoType\PropertyInfoType\Module1.vb:line 36 at PropertyInfoType.Module1.Main() in C:\Data\VBS.NET\PropertyInfoType\PropertyInfoType\Module1.vb:line 9
我搞不懂为什么这句代码会试图把"C0"转换成Int32?以下是复现问题的完整代码,请问如何通过反射获取的格式字符串来格式化属性值?我希望能在多个类上复用IPropertyHandler接口。
Module Module1 Sub Main() Dim I1 As Int32 = 31742 Dim P1 As String = "C0" Console.WriteLine("I1 = " & I1.ToString(P1) & ".") Dim F1 As Foo = New Foo(4217) Dim PropertyHandlerValue As String = GetPropertyString(Of Foo)(F1) Console.WriteLine("PropertyHandlerValue = " & PropertyHandlerValue.ToString & ".") Console.ReadLine() End Sub Public Function GetPropertyString(Of T)(ByVal TObject As T) As String GetPropertyString = Nothing If TypeOf TObject Is IPropertyHandler Then Dim SourceObject As IPropertyHandler = CType(TObject, IPropertyHandler) Dim HandlerPropertyName As String = SourceObject.PropertyHandlerName Dim HandlerFormatProvider As String = SourceObject.PropertyHandlerFormatProvider Dim SourcePropertyInfo As System.Reflection.PropertyInfo = GetType(T).GetProperty(HandlerPropertyName) If SourcePropertyInfo IsNot Nothing Then Dim SourcePropertyValue As Object = SourcePropertyInfo.GetValue(SourceObject) If String.IsNullOrEmpty(HandlerFormatProvider) Then GetPropertyString = SourcePropertyValue.ToString Else Dim Value As Object = System.Convert.ChangeType(SourcePropertyValue, SourcePropertyInfo.PropertyType) Dim Value2 As String = Value.ToString(HandlerFormatProvider) GetPropertyString = Value2 End If End If Else Throw New System.NotImplementedException("The TObject passed in does not implement IPropertyHandler.") End If End Function Public Interface IPropertyHandler ReadOnly Property PropertyHandlerName As String ReadOnly Property PropertyHandlerFormatProvider As String End Interface Public Class Foo Implements IPropertyHandler Private m_SomeInt32 As Int32 = 0 Private m_PropertyHandlerName As String = "SomeInt32" Private m_PropertyHandlerFormatProvider As String = "C0" 'Private m_PropertyHandlerFormatProvider As String = "" Public Sub New(ByVal SomeInt32 As Int32) m_SomeInt32 = SomeInt32 End Sub Public Property SomeInt32 As Int32 Get Return m_SomeInt32 End Get Set(value As Int32) m_SomeInt32 = value End Set End Property Public ReadOnly Property PropertyHandlerName As String Implements IPropertyHandler.PropertyHandlerName Get Return m_PropertyHandlerName End Get End Property Public ReadOnly Property PropertyHandlerFormatProvider As String Implements IPropertyHandler.PropertyHandlerFormatProvider Get Return m_PropertyHandlerFormatProvider End Get End Property End Class End Module
问题原因
当Value是Object类型时,调用ToString(String)会触发Visual Basic的扩展方法解析优先级问题:VB会优先选择Microsoft.VisualBasic.Strings.ToString这个扩展方法,而不是Int32.ToString(String)实例方法。前者的参数要求是转换目标类型(比如"Integer"),而不是格式字符串,所以它会尝试把"C0"转换成Integer类型,导致异常。
解决方法
要确保调用的是值类型的实例ToString(String)方法,有两种可行方案:
方案1:使用String.Format替代直接调用ToString
这是通用性最强的方案,无需担心类型匹配问题,直接修改格式化逻辑:
GetPropertyString = String.Format("{0:" & HandlerFormatProvider & "}", SourcePropertyValue)
方案2:通过IConvertible接口明确调用实例方法
如果需要更精细的控制,可以利用数值类型实现的IConvertible接口,明确指定格式字符串和文化信息:
If TypeOf SourcePropertyValue Is IConvertible Then Dim convertibleValue = DirectCast(SourcePropertyValue, IConvertible) GetPropertyString = convertibleValue.ToString(HandlerFormatProvider, System.Globalization.CultureInfo.CurrentCulture) Else GetPropertyString = SourcePropertyValue.ToString() End If
优化后的GetPropertyString方法示例
Public Function GetPropertyString(Of T)(ByVal TObject As T) As String GetPropertyString = Nothing If TypeOf TObject Is IPropertyHandler Then Dim SourceObject As IPropertyHandler = CType(TObject, IPropertyHandler) Dim HandlerPropertyName As String = SourceObject.PropertyHandlerName Dim HandlerFormatProvider As String = SourceObject.PropertyHandlerFormatProvider Dim SourcePropertyInfo As System.Reflection.PropertyInfo = GetType(T).GetProperty(HandlerPropertyName) If SourcePropertyInfo IsNot Nothing Then Dim SourcePropertyValue As Object = SourcePropertyInfo.GetValue(SourceObject) If String.IsNullOrEmpty(HandlerFormatProvider) Then GetPropertyString = SourcePropertyValue.ToString Else ' 使用String.Format实现通用格式化 GetPropertyString = String.Format("{0:" & HandlerFormatProvider & "}", SourcePropertyValue) End If End If Else Throw New System.NotImplementedException("传入的对象未实现IPropertyHandler接口。") End If End Function
额外说明
String.Format方案无需额外修改即可支持DateTime、Decimal等其他可格式化类型,完全满足多类复用IPropertyHandler接口的需求。
内容的提问来源于stack exchange,提问作者BandannaMan

