我希望我不会重复已经解决的问题,但我在类似的问题中找到了解决问题的方法却遇到了麻烦。我在论坛上找到了这个话题:Casting an object to two interfaces at the same time, to call a generic method。
我的问题也与序列化(XML)有关,我的泛型实现失败了,抛出了一个异常。为了更好地解释,请参阅下面的简化示例代码:
Public Interface IParent
Property ParentProp As String
End Interface
Public Interface IChild
Inherits IParent
Property ChildProp As String
End Interface
Public Class ExampleClass
Implements IChild
Public Property ChildProp As String = "Child Property" Implements IChild.ChildProp
Public Property ParentProp As String = "Parent Property" Implements IParent.ParentProp
End Class
Public Class ExampleListClass
Inherits List(Of Integer)
Implements IChild
Public Property ChildProp As String = "List Child Property" Implements IChild.ChildProp
Public Property ParentProp As String = "List Parent Property" Implements IParent.ParentProp
End Class
Public Class TestEnv
Public Shared Sub Main()
Dim str As String
Dim locExampleListClass = New ExampleListClass
str = TestEnv.Method1(Of Integer, ExampleListClass)(locExampleListClass)
MessageBox.Show(str, "locExampleListClass", MessageBoxButtons.OK, MessageBoxIcon.Information)
Dim locExampleClass = New ExampleClass
str = TestEnv.Method2(locExampleClass)
str = TestEnv.Method2_Dirty(locExampleClass)
MessageBox.Show(str, "locExampleClass", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
Public Shared Function Method1(Of T, C As {IList(Of T), IParent})(ByRef Instance As C) As String
If TypeOf (Instance) Is IChild Then
'Since instance is of type C which is restricted to IParent, the passed instance is a list which implements IChild (and of course IParent - because IChild inherits IParent)
Dim CastedInstance = DirectCast(Instance, IChild)
'!-----------------PseudoCode-----------------!
'Dim CastedInstance = DirectCast(Instance, {C, IChild})
Dim ReturnVal = TestEnv.Method2(CastedInstance)
Dim fake = TestEnv.Method2_Dirty(Instance)
Return ReturnVal
Else
'Something else is done (e.g. deactivating context menues only useful for IChild)
Return Instance.ParentProp
End If
'------------------------------------------------------------------------------------------
End Function
Public Shared Function Method2(Of T As {IChild})(ByRef Instance As T) As String
Return Instance.ChildProp
'Note: Xml-serialization into T when T is an interface seems not possible. Thus type C in Method1 needs to be maintained but extended to IChild
End Function
Public Shared Function Method2_Dirty(Of T As {IParent})(ByRef Instance As T) As String
'This would work but is not very nice (there is a reason why T shall be restricted to IChild in the first place - no ifs or trycasts needed)
If TypeOf (Instance) Is IChild Then
Return DirectCast(Instance, IChild).ChildProp
Else
Throw New Exception("The input parameter needs to be of type IChild but I am too stupid to make it work")
End If
End Function
End Class这里有两个示例类,一个是list,非常简单。两者都实现了接口IChild。但是,Method1仅将输入限制为IParent,并检查是否实现了IChild。如果不是,就会做一些其他的事情。如果是,则可以毫不费力地强制转换实例。因此,在这一点上,我知道实例实现了IList(of T)和IChild。现在可以使用强制转换的实例调用Method2。所有这些显然都是编译和工作的。我的问题是,在我的例子中,Method2是一个反序列化程序(试图通过传递实例ByRef来指明这一点)。因为CastedInstance的类型是IChild,所以反序列化程序抛出一个异常。
因此,我仍然需要类型C,但通过IChild进行了扩展。我想我可以实现仅限于IParent的Method2_Dirty,并执行类型检查和尝试转换。尽管如此,这似乎并不是很好,因为异常是在运行时抛出的,而不是在编译前的编码过程中抛出的。
正如开头所说的,我希望我不会重复任何问题,并期待您的反馈。谢谢
发布于 2020-04-11 06:23:48
经过一段时间的调查,我找到了一个令我满意的解决方案。我想与社区分享它。也许这对将来的某个人有帮助。如果有人有更好的解决方案,请让我知道…
我意识到我的问题实际上与XML序列化有关(使用二进制序列化不会发生异常)。谢谢Aybe把我推向这个方向。我猜这个问题可以用DataContractSerializer类来解决。然而,我实现了其他更接近本文主题的东西。
下面是初始示例的扩展,它更清楚地解决了这个问题(我希望)。它现在包含反序列化,以显示实际问题是什么。抱歉,时间太长了
Public Interface IParent
Property ParentProp As String
End Interface
Public Interface IImportable
Inherits IParent
Property ChildProp As String
End Interface
<Serializable> Public Class ExampleClass
Implements IImportable
Public Property ChildProp As String = "Child Property" Implements IImportable.ChildProp
Public Property ParentProp As String = "Parent Property" Implements IParent.ParentProp
End Class
<Serializable> Public Class ExampleClass_NonImportableList
Inherits List(Of Integer)
Implements IParent
Public Property ParentProp As String = "Parent Property" Implements IParent.ParentProp
End Class
<Serializable> Public Class ExampleClass_List
Inherits List(Of Integer)
Implements IImportable
Public Property ChildProp As String = "List Child Property" Implements IImportable.ChildProp
Public Property ParentProp As String = "List Parent Property" Implements IParent.ParentProp
End Class
Public Class TestEnv
Private Enum En_DeSerializationMode
Xml
Binary
End Enum
Public Shared Sub Main()
'Define some instances
Dim locExampleClass = New ExampleClass
Dim locExampleClass_NonImportableList = New ExampleClass_NonImportableList From {1, 3, 5, 7}
Dim locExampleClass_List = New ExampleClass_List From {11, 13, 15, 17}
'Set mode and filenames
'Dim SeriMode = En_DeSerializationMode.Binary
Dim SeriMode = En_DeSerializationMode.Xml
Dim FileExt = If(SeriMode = En_DeSerializationMode.Binary, ".bin", ".xml")
Dim File1 = New IO.FileInfo("ExampleClass" & FileExt)
Dim File2 = New IO.FileInfo("ExampleClass_NonImportableList" & FileExt)
Dim File3 = New IO.FileInfo("ExampleClass_List" & FileExt)
'Export the instances
TestEnv.ExportToFile(File1, SeriMode, locExampleClass)
TestEnv.ExportToFile(File2, SeriMode, locExampleClass_NonImportableList)
TestEnv.ExportToFile(File3, SeriMode, locExampleClass_List)
'Import form the files and add to lists
'Obviously complier error: TestEnv.CheckImportFromFile(Of Integer, ExampleClass)(New IO.FileInfo(File1 & FileExt), SeriMode, locExampleClass)
TestEnv.ImportFromFile(File1, SeriMode, locExampleClass) 'Direct import can be done
TestEnv.CheckImportFromFile(Of Integer, ExampleClass_NonImportableList)(File2, SeriMode, locExampleClass_NonImportableList)
TestEnv.CheckImportFromFile(Of Integer, ExampleClass_List)(File3, SeriMode, locExampleClass_List)
End Sub
Private Shared Sub ExportToFile(Of C)(ExportFile As IO.FileInfo, Mode As En_DeSerializationMode, InstanceTarget As C)
'Set the serialization routine according to the mode
Dim LambdaSerializer As Action
Select Case Mode
Case En_DeSerializationMode.Binary
LambdaSerializer = Sub()
Dim locFs = New IO.FileStream(ExportFile.FullName, IO.FileMode.Create)
Using locFs
Dim locBinaryFormatter = New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(Nothing,
New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.File))
locBinaryFormatter.Serialize(locFs, InstanceTarget)
locFs.Flush()
locFs.Close()
End Using
End Sub
Case Else 'En_DeSerializationMode.Xml
LambdaSerializer = Sub()
Dim locXmlWriter = New System.Xml.Serialization.XmlSerializer(InstanceTarget.GetType)
Dim locXmlFile = New IO.StreamWriter(ExportFile.FullName)
Using locXmlFile
locXmlWriter.Serialize(locXmlFile, InstanceTarget)
locXmlFile.Flush()
locXmlFile.Close()
End Using
End Sub
End Select
'Serialize the instance to the file
Try
LambdaSerializer.Invoke()
Catch ex As Exception
MessageBox.Show(ex.Message, "Export error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Private Shared Sub CheckImportFromFile(Of T, C As {IList(Of T), IParent})(ImportFile As IO.FileInfo, Mode As En_DeSerializationMode, ByRef InstanceTarget As C)
'One dirty solution would be of course to implement IImportable to each class passed to CheckImportFromFile: But this would make no sense, craete effort and would just satisfy the compiler
If TypeOf (InstanceTarget) Is IImportable Then
Dim Imported As IImportable = Nothing
'Dim Imported As C 'This creates a compiler error because C does not implement IImportable
'Dim Imported As {C,IImportable} 'PseudoCode!!!
TestEnv.ImportFromFile(ImportFile, Mode, Imported) ' This causes an exception when using Xml deserialization (binary works)
If Imported IsNot Nothing Then
Dim ImportedList As C
Try
'Try block to prevent that an instance is imported that by incident fulfills the C restrictions but is not of InstanceTarget type
ImportedList = DirectCast(Imported, C)
Catch ex As Exception
MessageBox.Show("The imported instance of type " & Imported.GetType.ToString & " could not be converted to the target type " & GetType(C).ToString, "Import error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
End Try
'Add the imported items to the target instance list
For Each locItem In ImportedList
InstanceTarget.Add(locItem)
Next
End If
'Without restriction but with runtime checks
Dim Imported2 As C
TestEnv.ImportFromFile_RuntimeChecks(ImportFile, Mode, Imported2)
If Imported2 IsNot Nothing Then
'Now no trycast to C (respectively IList) is needed
For Each locItem In Imported2
InstanceTarget.Add(locItem)
Next
End If
'Final solution
Dim Imported3 As C
TestEnv.ImportFromFile_Final(ImportFile, Mode, Imported3, Function(locImported)
'This makes programmer aware that the imported instance has to be importable (try block in ImportFromFile method)
Return DirectCast(locImported, IImportable)
End Function)
If Imported3 IsNot Nothing Then
'Now no trycast to C (respectively IList) is needed
For Each locItem In Imported3
InstanceTarget.Add(locItem)
Next
End If
Else
'Something else is done (e.g. deactivating context menues)
MessageBox.Show("Instance of type " & GetType(C).ToString & " will not be imported", "No import", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End Sub
Private Shared Sub ImportFromFile(Of C As {IImportable})(ImportFile As IO.FileInfo, SerializationMode As En_DeSerializationMode, ByRef ImportedInstance As C)
ImportedInstance = TestEnv.FileDeserializer(Of C)(ImportFile, SerializationMode)
If ImportedInstance Is Nothing Then Exit Sub
'Do something after import
ImportedInstance.ChildProp += " (I was imported)"
End Sub
Private Shared Sub ImportFromFile_RuntimeChecks(Of C As {IParent})(ImportFile As IO.FileInfo, SerializationMode As En_DeSerializationMode, ByRef ImportedInstance As C)
ImportedInstance = TestEnv.FileDeserializer(Of C)(ImportFile, SerializationMode)
If ImportedInstance Is Nothing Then Exit Sub
'This would work but is not very nice (there is a reason why C shall be restricted to IImportable in the first place - no ifs or trycasts needed)
If TypeOf (ImportedInstance) Is IImportable Then
'Do something after import
DirectCast(ImportedInstance, IImportable).ChildProp += " (I was imported with RuntimeChecks)"
Else
Throw New Exception("The input parameter needs to be of type IImportable but I (or the compiler :-)) am too stupid to make it work")
End If
End Sub
Private Shared Sub ImportFromFile_Final(Of C As {IParent})(ImportFile As IO.FileInfo, SerializationMode As En_DeSerializationMode, ByRef ImportedInstance As C, ImportedCaster As Func(Of C, IImportable))
ImportedInstance = TestEnv.FileDeserializer(Of C)(ImportFile, SerializationMode)
If ImportedInstance Is Nothing Then Exit Sub
Dim ImportedInstanceCast As IImportable
Try
ImportedInstanceCast = ImportedCaster.Invoke(ImportedInstance)
Catch ex As Exception
MessageBox.Show("Instance of type " & ImportedInstance.GetType.ToString & " cannot be converted to IImportable", "No import", MessageBoxButtons.OK, MessageBoxIcon.Information)
Exit Sub
End Try
'Do something after import
ImportedInstanceCast.ChildProp += " (I was imported smart)"
End Sub
Private Shared Function FileDeserializer(Of C)(ImportFile As IO.FileInfo, SerializationMode As En_DeSerializationMode) As C
'Check that the file exists
If Not ImportFile.Exists Then
MessageBox.Show("File does not exist", "File error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return Nothing
End If
'Define reader
Dim LambdaDeserializer As Func(Of Object)
Select Case SerializationMode
Case En_DeSerializationMode.Binary
LambdaDeserializer = Function()
Dim locFs = New IO.FileStream(ImportFile.FullName, IO.FileMode.Open)
Dim locObject As Object
Using locFs
Dim locBinaryFormatter = New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(Nothing,
New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.File))
locObject = locBinaryFormatter.Deserialize(locFs)
locFs.Close()
End Using
Return locObject
End Function
Case Else 'En_DeSerializationMode.Xml
LambdaDeserializer = Function()
Dim locXmlReader = New System.Xml.Serialization.XmlSerializer(GetType(C))
Dim locXmlFile = New IO.StreamReader(ImportFile.FullName)
Dim locObject As Object
Using locXmlFile
locObject = locXmlReader.Deserialize(locXmlFile)
locXmlFile.Close()
End Using
Return locObject
End Function
End Select
'Deserialize the file
Dim ImportedInstance As C
Try
Dim DeseriObj = LambdaDeserializer.Invoke
ImportedInstance = DirectCast(DeseriObj, C)
Catch ex As Exception
MessageBox.Show("Error during deserialization: " & ex.Message, "Import error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
Return ImportedInstance
End Function
End Class来描述一下这个例子:
second version ImportFromFile_RuntimeChecks中的TypeOf检查)
我认为这在代码通信方面是足够的。如上所述,如果有人有更好的想法,请张贴…
https://stackoverflow.com/questions/61070680
复制相似问题