首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将泛型类型扩展到继承接口

将泛型类型扩展到继承接口
EN

Stack Overflow用户
提问于 2020-04-07 07:48:14
回答 1查看 64关注 0票数 0

我希望我不会重复已经解决的问题,但我在类似的问题中找到了解决问题的方法却遇到了麻烦。我在论坛上找到了这个话题:Casting an object to two interfaces at the same time, to call a generic method

我的问题也与序列化(XML)有关,我的泛型实现失败了,抛出了一个异常。为了更好地解释,请参阅下面的简化示例代码:

代码语言:javascript
复制
    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,并执行类型检查和尝试转换。尽管如此,这似乎并不是很好,因为异常是在运行时抛出的,而不是在编译前的编码过程中抛出的。

正如开头所说的,我希望我不会重复任何问题,并期待您的反馈。谢谢

EN

回答 1

Stack Overflow用户

发布于 2020-04-11 06:23:48

经过一段时间的调查,我找到了一个令我满意的解决方案。我想与社区分享它。也许这对将来的某个人有帮助。如果有人有更好的解决方案,请让我知道…

我意识到我的问题实际上与XML序列化有关(使用二进制序列化不会发生异常)。谢谢Aybe把我推向这个方向。我猜这个问题可以用DataContractSerializer类来解决。然而,我实现了其他更接近本文主题的东西。

下面是初始示例的扩展,它更清楚地解决了这个问题(我希望)。它现在包含反序列化,以显示实际问题是什么。抱歉,时间太长了

代码语言:javascript
复制
    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

来描述一下这个例子:

  • 一些实例被创建并序列化为files
  • Afterwards它们需要再次反序列化/导入
  • 要点是在导入之后应该对导入的实例做一些处理(在这个例子中只有ChildProperty字符串被改变)
  • 在最后一步,导入的列表项被添加到初始列表中。因此,类型必须匹配,这是由泛型完成的(对这个问题不太重要,但为了完成我想做的事情)
  • 第一版ImportFromFile为Xml序列化带来了问题,这就是为什么我写这篇文章(如果我们能以某种方式扩展泛型类型C就好了,因为我们知道它会起作用-参见

second version ImportFromFile_RuntimeChecks中的TypeOf检查)

  • 最终版本ImportFromFile_Final需要一个额外的输入参数函数,它将导入的实例与所需的类型进行配对,以更改Child属性。我认为这个解决方案是足够的,因为这样程序员(最有可能是我自己)知道这个造型必须工作,因此接口应该在类中实现。

我认为这在代码通信方面是足够的。如上所述,如果有人有更好的想法,请张贴…

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61070680

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档