I can format an Int32, I1, as currency - it displays: I1 = $31,742.
But I cannot format an Int32 property of a class whose PropertyInfo and whose value and whose format is determined by reflection. Inside GetPropertyString and inside debugger, Value has a .GetType of Int32, which is correct. But, I get an exception on the line "Dim Value2 As String = Value.ToString(HandlerFormatProvider)". The exception is:
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
I am very confused as to why, based on the exception, that statement appears to be trying to convert "C0" to an Int32??? The program that replicates the problem is as follows:
How do I format the reflected property value with a reflected format string? Obviously, I want to use the IPropertyHandler Interrface on many classes.
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
It seems as though you are trying to check whether SourcePropertyValue has a ToString() method that supports customized formatting, and if so, use it. But if not, use the default ToString(). If that's what you are doing, the correct way to do it in .NET is to check to see whether SourcePropertyValue implements IFormattable:
The
IFormattableinterface converts an object to its string representation based on a format string and a format provider.
All .NET primitive types (other that string) implement this interface, so if you rewrite your code to use the interface when possible, your code should work correctly without needing to worry about runtime errors due to late binding failures:
If SourcePropertyInfo IsNot Nothing Then
Dim SourcePropertyValue As Object = SourcePropertyInfo.GetValue(SourceObject)
Dim FormattableValue as IFormattable = TryCast(SourcePropertyValue, IFormattable)
If FormattableValue Is Nothing Or String.IsNullOrEmpty(HandlerFormatProvider) Then
GetPropertyString = IF(SourcePropertyValue, "").ToString()
Else
GetPropertyString = FormattableValue.ToString(HandlerFormatProvider, CultureInfo.CurrentCulture)
End If
End If
Demo fiddle #1 here.
As an aside, you seem to have made your debugging more difficult by not setting Option Strict On. If you were to have enabled it, you would have seen that the following line:
Dim Value2 As String = Value.ToString(HandlerFormatProvider)
has a compilation error:
Option Strict On disallows implicit conversions from 'String' to 'Integer'.
The compilation error is... not very helpful, however Value is declared to be of type System.Object whose ToString() method does not have an overload that takes a format string -- so I would expect to see some sort of compilation error there.
Demo fiddle #2 here.
The fact that the code fails to compile in strict mode means that its runtime behavior in non-strict mode will depend on the vagaries of VB.NET's late binding logic. Sometimes that works nicely for you, but sometimes not. And when it doesn't work, it can be very hard to reason out why; your code is a good example of this very problem, I'm still not sure exactly why you are getting the precise exception you see.
For comparison, my fixed code above compiles with Option Strict On so will have no unexpected late-binding surprises.
So, unless you need late binding for some reason (e.g. because you are working with COM objects), I'd recommend to always enable Option Strict On by default precisely because it allows you to find errors at compile time rather than runtime. And the earlier you find an error, the cheaper it will be to fix. For further discussion, see:
Update I was eventually able to understand why you were getting the Conversion from string "C0" to type 'Integer' is not valid. error when using Option Strict Off. If I change the call to ToString() to pass in a CultureInfo like so:
Dim Value2 As String = Value.ToString(HandlerFormatProvider, CultureInfo.CurrentCulture)
Then I get a compilation error even with Option Strict Off:
Too many arguments to 'Public Overloads ReadOnly Default Property Chars(index As Integer) As Char'.
What that tells me is that VB.NET's late-binding code interpreted
Value.ToString(HandlerFormatProvider)
To be
Value.ToString()(Convert.ToInt32(HandlerFormatProvider))
I.e. a call to Object.ToString() to get a string, then an index into the string to get a specific character at the index given by HandlerFormatProvider when cast to an integer. Clearly not what you were hoping, which seems to have been CType(Value, Int32).ToString(HandlerFormatProvider). Just goes to show why Option Strict On can be easier to work with despite being, well, strict.
Demo fiddle #3 here.