【问题标题】:How to create an extension method that works with a subclass of a nested type parameter?如何创建与嵌套类型参数的子类一起使用的扩展方法?
【发布时间】:2020-10-20 00:35:52
【问题描述】:

我在访问我创建的将带有基类的对象作为其类型参数的扩展方法时遇到问题。

这是基类:

Namespace Models.Db
  Public Class Base
    Public Property Id As Integer
  End Class
End Namespace

...和子类:

Namespace Models.Db
  Public Class City
    Inherits Base

    ...

  End Class
End Namespace

扩展方法如下:

Imports System.Collections.Generic
Imports System.Data.Entity
Imports System.Linq
Imports System.Runtime.CompilerServices
Imports Matrix.Models
Imports Moq

Public Module Extensions
  <Extension>
  Public Sub SetupMock(Instance As Mock(Of DbSet(Of Db.Base)), Entities As List(Of Db.Base))
    Instance.As(Of IQueryable(Of Db.Base)).Setup(Function(Cities) Cities.GetEnumerator).Returns(Entities.GetEnumerator)
    Instance.As(Of IQueryable(Of Db.Base)).Setup(Function(Cities) Cities.ElementType).Returns(Entities.AsQueryable.ElementType)
    Instance.As(Of IQueryable(Of Db.Base)).Setup(Function(Cities) Cities.Expression).Returns(Entities.AsQueryable.Expression)
    Instance.As(Of IQueryable(Of Db.Base)).Setup(Function(Cities) Cities.Provider).Returns(Entities.AsQueryable.Provider)
  End Sub
End Module

...这是调用代码:

Private Function GetDbContextMock() As Mock(Of Db.Context)
  Dim oCitiesMock As Mock(Of DbSet(Of Db.City))

  oCitiesMock = New Mock(Of DbSet(Of Db.City))
  oCitiesMock.SetupMock(Me.Cities) ' Me.Cities is a List(Of Db.City)
End Function

编译时错误是:

“SetupMock”不是“Mock(Of DbSet(Of City))”的成员

但是,当我将调用代码更改为:

  Dim oCitiesMock As Mock(Of DbSet(Of Db.Base))
  oCitiesMock = New Mock(Of DbSet(Of Db.Base))

...找到扩展方法。但我需要发送扩展方法的子类引用,而不是基类引用。

This answer 表示可以使用简单的超类/子类对象,但由于这是处理类型参数,所以有点复杂。

This answer 起初似乎可以提供一个解决方案,但经过进一步思考,它只解决了一个级别的类型参数。上面的代码有两个。我不确定这种细微差别是否会产生影响,但最终代码无法编译。

这可以在 .NET 中完成吗?

【问题讨论】:

    标签: vb.net inheritance extension-methods type-parameter


    【解决方案1】:

    您的第二个链接答案是正确的。打字的额外“水平”并不重要。该答案的关键点是在使用派生类型定义扩展方法时包括泛型(C# 中的&lt;T&gt;,VB 中的(Of T))。您的初始代码没有这样做 - 期望基本类型是硬编码。

    以这种方式使用泛型时,您可以添加一个约束,以便T 必须继承所需的基类型。 (从技术上讲,这适用于任何通用方法,因此扩展方法也以这种方式工作很方便。)

    见下文,以控制台应用程序的形式呈现,以便于测试:

    Option Strict On
    Imports System.Runtime.CompilerServices
    
    Module Module1
        Sub Main()
            Dim mock As New Mock(Of [Set](Of Derived))
            mock.SetupMock(New List(Of Derived))
    
            'also compiles, uses same extension
            Dim mockBase As New Mock(Of [Set](Of Base))
            mockBase.SetupMock(New List(Of Base))
        End Sub
    End Module
    
    Public Module Extensions
        <Extension>
        Public Sub SetupMock(Of T As Base)(Instance As Mock(Of [Set](Of T)), Entities As List(Of T))
            'do stuff with Entities
        End Sub
    End Module
    
    'simple class definitions to illustrate the example
    Public Class Base : End Class
    Public Class Derived
        Inherits Base
    End Class
    Public Class [Set](Of T) : End Class
    Public Class Mock(Of T) : End Class
    

    如果您的实体列表始终是 Base 类型的列表,但恰好填充了派生类型,则可以改为使用 Entities As List(Of Base) 定义扩展方法,但您的列表最好始终为 List(Of Base)而不是List(Of T)

    【讨论】:

    • 就是这样,谢谢。我最终不得不在Entities As List(Of T)Entities As IEnumerable(Of Db.Base) 之间进行选择。我和前者一起去了。它看起来更性感。
    • @InteXX IEnumerable(Of T) 更加灵活。 (我努力双关语灵活和性感;没有任何好处。)
    • @InteXX 请注意,IEnumerable 的参数是变体(我相信您可以使用IEnumerable(Of Derived),其中预期IEnumerable(Of Base) ---我忘记这是协变还是逆变)。差异可能会在这里发挥作用,我建议您阅读一下。
    • 其实最后我一个都没用。我的两个实体——RoleUser——不是从 Base 派生的,而是分别从 IdentityRoleIdentityUser 派生的。其他地方的代码,我无法更改,明确需要Db.RoleDb.User 类型,所以我不得不完全跳过扩展方法方法。但是,是的,在我的选角过程中,我确实注意到IEnumerable(Of T) 似乎效果更好。谢谢你的提及。 | @克雷格
    • 好的,我能够恢复扩展方法。我将方法的类型参数从Setup(Of TEntity As Db.Base) 修改为Setup(Of TEntity As Class)。这处理Db.Base 以及IdentityRoleIdentityUser。它运作良好。 | @克雷格
    猜你喜欢
    • 2019-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-10
    • 1970-01-01
    相关资源
    最近更新 更多