【问题标题】:Extend generic type to inherited interface将泛型类型扩展到继承的接口
【发布时间】:2020-07-19 02:14:36
【问题描述】:

我希望我不要重复已经解决的问题,但是我在类似问题中找不到解决问题的方法。 我在论坛里发现了这个话题:Casting an object to two interfaces at the same time, to call a generic method。老实说,我的 C# 非常糟糕,因为我只在 VB.NET 中编码,而且我认为我的问题有点不同(继承接口而不是独立接口)。

我的问题也与序列化 (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

所以有两个示例类,一个是列表并且非常简单。两者都实现了接口 IChild。然而,方法 1 将 inputargument 限制为 IParent 并检查 IChild 是否已实现。如果没有做其他事情。如果是,则可以毫无问题地投射实例。因此,此时我知道该实例实现了 IList(of T) 和 IChild。 现在可以使用强制转换的实例调用 Method2。所有这些显然都可以编译和工作。我的问题是 Method2 在我的例子中是一个反序列化器(试图通过传递 Instance ByRef 来表明这一点)。由于 CastedInstance 是 IChild 类型,因此反序列化器会引发异常。

因此,我仍然需要类型 C,但由 IChild 扩展。在 Method1 中的 if 语句之后,我知道该实例满足了限制,但我未能实现正确的代码(请参阅注释 PseudeCode)。 我想我可以实现 Method2_Dirty ,它只限于 IParent 并进行类型检查和 trycasts。尽管如此,这似乎不是很好,因为异常是在运行时引发的,而不是在编译前的编码过程中引发的。

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

【问题讨论】:

  • TL;DR; DataContractSerializer 不能胜任这项工作吗?
  • 我担心魔鬼可能存在于您忽略的细节中。没有什么让我觉得明显是错误的,尽管我会注意到约束并不一定会像你认为的那样考虑“明显”的类型关系——也可以看看 Eric Lippert 几年来的博客条目回到这里:blogs.msdn.microsoft.com/ericlippert/2012/03/09/…
  • 另一件事是,即使您编写了可以满足您的类型检查的编码类型,编译器也不一定足够聪明来解决这个问题。在这种情况下,您可能需要进行运行时类型检查,但如果设置得当,应该可以有效保证成功。
  • 感谢两位的回答。 Craig 提供的博客让我笑了(虽然对泛型笑了起来很讨厌 :-))。我同意 Craig 的观点,感觉有些不对劲,但同时我觉得这样的扩展功能会很好。但随后我们又回到了博客……希望我能在接下来的日子里有时间多考虑一下,看看 Aybe 建议的 DataContractSerializer。我也在考虑重载我的 Method2 以获得两个版本:一个用于运行时检查,一个用于编译器检查......

标签: vb.net generics interface casting deserialization


【解决方案1】:

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

我意识到我的问题实际上与 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

稍微描述一下例子:

  • 一些实例被创建并序列化为文件
  • 之后它们应再次反序列化/导入
  • 重点是在导入之后,应该对导入的实例进行一些处理(在本例中,仅更改了 ChildProperty 字符串)
  • 在最后一步,将导入的列表项添加到初始列表中。因此类型必须匹配,这是由泛型完成的(对于这个问题来说不是太重要,但要完成我想做的事情)

  • 第一个版本 ImportFromFile 为 Xml 序列化带来了问题,这就是我创建这篇文章的原因(如果我们可以以某种方式扩展泛型 C 会很好,因为我们知道它会起作用 - 请参阅 CheckImportFromFile 中的 TypeOf 检查)

  • 第二个版本 ImportFromFile_RuntimeChecks 使用运行时检查,这可能是可行的,但显然对程序员来说不太好(没有迹象表明该方法在编码期间需要什么)
  • 最终版本 ImportFromFile_Final 需要一个额外的输入参数函数,它将导入的实例解析为所需的类型以更改子属性。我认为这个解决方案就足够了,因为这样程序员(最可能是我自己)知道这个转换必须工作,因此接口应该在类中实现。

我认为这在代码通信方面已经足够了。如前所述,如果有人有更好的主意,请发布...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-25
    • 1970-01-01
    • 2020-03-02
    • 2020-12-01
    相关资源
    最近更新 更多