类似于接口或泛型的派生类

时间:2019-05-16 12:35:12

标签: .net vb.net generics interface derived-class

我有两个业务模型的实现,有点像下面的框架。

Public Class Class1(Of T)
    Public Property Property1 As String
    Public Property Property2 As String
    Public Property Property3 As Decimal

    Public Sub Method1(arg1 As T, arg2 As String)

    End Sub
    Public Sub Method2(arg1 As T, arg2 As String)

    End Sub
    Public Sub Method3(arg1 As T, arg2 As String, arg3 as Integer)

    End Sub
End Class

Public Class Class1
    Public Property Property1 As String
    Public Property Property2 As String
    Public Property Property3 As Decimal

    Public Sub Method1(Of T)(arg1 As T, arg2 As String)

    End Sub
    Public Sub Method2(Of T)(arg1 As T, arg2 As String)

    End Sub
    Public Sub Method3(Of T)(arg1 As T, arg2 As String, arg3 as Integer)

    End Sub
End Class

第一个实现是泛型类,而第二个实现具有泛型方法。

我想确保两个类都应具有相同的属性和方法。 开发人员可能在一个类中添加了一个方法,却忘记了添加另一个类,这是我想避免的。随着类的变大,很难手动确保这一点,因为两个类中的方法可能没有相同的顺序。

由于泛型(一个具有(Of T)而另一个则不具有)的定义不同,所以一个接口不起作用。

类似派生的类概念不起作用,因为每个方法将具有2个具有相同签名的定义。

有没有出路?

2 个答案:

答案 0 :(得分:1)

不,在VB中无法做到这一点。最接近的事情是为这两个都有单独的接口,例如:

Public Interface IGenericMethods
    Sub Method1(Of T)(arg1 As T, arg2 As String)
    Sub Method2(Of T)(arg1 As T, arg2 As String)
    Sub Method3(Of T)(arg1 As T, arg2 As String, arg3 as Integer)
End Interface

Public Interface IGenericClass(Of T)
    Sub Method1(arg1 As T, arg2 As String)
    Sub Method2(arg1 As T, arg2 As String)
    Sub Method3(arg1 As T, arg2 As String, arg3 as Integer)
End Interface

Public Class GenericMethods
    Inherits IGenericMethods

    ' ...
End Class

Public Class GenericClass(Of T)
    Inherits IGenericClass(Of T)

    ' ...
End Class

至少您只有这两个接口同步,但是,正如您已经知道的那样,仍然没有办法强制两个接口保持同步。我唯一想到的方法是添加一个单元测试,该单元测试使用反射来比较两个接口,并在两个接口不同时生成失败结果。至少然后,您可以使单元测试的运行自动化,这样,只要对一个进行了更改,而对另一个却没有进行任何更改,就可以立即得到通知。

或者,由于两个类似乎都在做同一件事,因此将两者合并为一个类可能更有意义:

Public Class Class1(Of T)
    Public Property Property1 As String
    Public Property Property2 As String
    Public Property Property3 As Decimal

    Public Sub Method1(arg1 As T, arg2 As String)
    End Sub

    Public Sub Method2(arg1 As T, arg2 As String)
    End Sub

    Public Sub Method3(arg1 As T, arg2 As String, arg3 as Integer)
    End Sub

    Public Sub Method1(Of T2)(arg1 As T2, arg2 As String)
    End Sub

    Public Sub Method2(Of T2)(arg1 As T2, arg2 As String)
    End Sub

    Public Sub Method3(Of T2)(arg1 As T2, arg2 As String, arg3 as Integer)
    End Sub
End Class

这仍然不会强制类同时具有每个方法的两个版本,但是至少它全部在同一个类中,这很明显,如果您更改一个,则必须更改另一个,尤其是添加一些对此发表评论。

最后一个选择是使用代码生成工具(例如T4模板)从同一脚本自动生成两个类。这样,当您需要进行更改时,只需在脚本中进行一次更改,然后两个类将自动重新生成以匹配。

答案 1 :(得分:1)

感谢所有帮助我解决该问题的人。我最终创建了一个T4模板。出于各种原因,建议的所有其他选项均不合适。

对于像我这样的人,这就是我所做的:

  1. 具有所有业务实现的我的类(此处省略了类名和实现)有点像这样。
Public Class Class1
    Public Property Property1 As String
    Public Property Property2 As String
    Public Property Property3 As Decimal

    Public Sub Method1(Of T)(arg1 As T, arg2 As String)
        Throw New NotImplementedException
    End Sub
    Public Sub Method2(Of T)(arg1 As T, arg2 As String)
        Throw New NotImplementedException
    End Sub
    Public Sub Method3(Of T)(arg1 As T, arg2 As String, arg3 As Integer)
        Throw New NotImplementedException
    End Sub
    Public Function Function1(Of T)(arg1 As T, arg2 As String) As String
        Throw New NotImplementedException
    End Function
    Public Function Function2(Of T)(arg1 As T, arg2 As String) As List(Of T)
        Throw New NotImplementedException
    End Function
    Public Function Function3(Of T)(arg1 As T, arg2 As String, arg3 As Integer) As Decimal
        Throw New NotImplementedException
    End Function
End Class
  1. 我向名为Class1OfT.tt的项目添加了一个T4模板,并设置了其属性Build Action = NoneCustom Tool = TextTemplatingFileGenerator

  2. 在我的Class1OfT.tt文件中添加了以下代码:

<#@ template debug="true" hostspecific="true" language="VB" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.VisualBasic" #>
<#@ output extension=".vb" #>

'------------------------------------------------------------------------------
' <auto-generated>
'     This code was generated from a template.
'
'     Manual changes to this file may cause unexpected behavior in your application.
'     Manual changes to this file will be overwritten if the code is regenerated.
' </auto-generated>
'------------------------------------------------------------------------------
<#
  Const ClassName = "Class1"
#>

Option Strict On
Option Compare Text
Imports System.Data.SqlClient

Public Class <#= ClassName #>(Of T)
    Private ClsObj As <#= ClassName #>
<#
 Dim reProperty As New Regex("Public Property (?<name>\w+)(?<sig>(?: As [^=]+))", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
 Dim reFunction As New Regex("Public Function (?<name>\w+)(?<of>\(Of [^\)]*\))?(?<sig>\(.*\)(?: As .+)?)", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
 Dim reSub As New Regex("Public Sub (?<name>\w+)(?<of>\(Of [^\)]*\))?(?<sig>\(.*\))", RegexOptions.Compiled Or RegexOptions.IgnoreCase)
 Dim absolutePath As String = Host.ResolvePath(ClassName & ".vb")
 Dim contents As String = IO.File.ReadAllText(absolutePath)
 contents = contents.Substring(contents.IndexOf("Public Class " & ClassName))
 contents = contents.Substring(0, contents.IndexOf("End Class"))
 contents = contents.Replace(vbTab, " ")
 For Each line As String In Split(contents, vbNewLine)
  line = Trim(line)
  If line Like "Public ReadOnly Property *" Then
#>

    <#= line #>
<#
  ElseIf line Like "Public Property *" Then
    Dim groups = reProperty.Match(line).Groups
#>

    Public Property <#= groups("name").Value #><#= groups("sig").Value.TrimEnd #>
        Get
            Return ClsObj.<#= groups("name").Value #>
        End Get
        Set(value<#= groups("sig").Value.TrimEnd #>)
            ClsObj.<#= groups("name").Value #> = value
        End Set
    End Property
<#
  ElseIf line Like "Public Function *" Then
    Dim groups = reFunction.Match(line).Groups
#>

    Public Function <#= groups("name").Value #><#= groups("sig").Value #>
        Return ClsObj.<#= GetFnDef(groups("name").Value, groups("of").Value, groups("sig").Value) #>
    End Function
<#
  ElseIf line Like "Public Sub *" AndAlso Not line Like "Public Sub New(*" Then
    Dim groups = reSub.Match(line).Groups
#>

    Public Sub <#= groups("name").Value #><#= groups("sig").Value #>
        ClsObj.<#= GetFnDef(groups("name").Value, groups("of").Value, groups("sig").Value) #>
    End Sub
<#
  End If
 Next
#>
End Class

<#+
    Function GetFnDef(name As String, ofPart As String, fnArgs As String) As String
        Static reArgName As New RegularExpressions.Regex("^\w+$", RegularExpressions.RegexOptions.Compiled)
        If fnArgs.StartsWith("(") Then fnArgs = fnArgs.SubString(1)
        Dim parts() As String = Split(fnArgs)
        Dim args = Enumerable.Range(0, parts.Length).Where(Function(n) parts(n) = "As").Select(Function(n) parts(n - 1)).ToList
        For i As Integer = args.Count - 1 To 0 Step -1
            If args(i) Like "*()" Then args(i) = args(i).Substring(0, args(i).Length - 2)
            If Not reArgName.IsMatch(args(i)) Then args.RemoveAt(i)
        Next
        If String.IsNullOrEmpty(ofPart) Then
            Return String.Concat(name, "(", Join(args.ToArray, ", "), ")")
        Else
            Return String.Concat(name, "(Of T)(", Join(args.ToArray, ", "), ")")
        End If
    End Function
#>
  1. 保存文件后,将生成一个名为Class1OfT.vb的新文件,其中包含以下代码。正是我所需要的。
'------------------------------------------------------------------------------
' <auto-generated>
'     This code was generated from a template.
'
'     Manual changes to this file may cause unexpected behavior in your application.
'     Manual changes to this file will be overwritten if the code is regenerated.
' </auto-generated>
'------------------------------------------------------------------------------

Option Strict On
Option Compare Text
Imports System.Data.SqlClient

Public Class Class1(Of T)
    Private ClsObj As Class1

    Public Property Property1 As String
        Get
            Return ClsObj.Property1
        End Get
        Set(value As String)
            ClsObj.Property1 = value
        End Set
    End Property

    Public Property Property2 As String
        Get
            Return ClsObj.Property2
        End Get
        Set(value As String)
            ClsObj.Property2 = value
        End Set
    End Property

    Public Property Property3 As Decimal
        Get
            Return ClsObj.Property3
        End Get
        Set(value As Decimal)
            ClsObj.Property3 = value
        End Set
    End Property

    Public Sub Method1(arg1 As T, arg2 As String)
        ClsObj.Method1(Of T)(arg1, arg2)
    End Sub

    Public Sub Method2(arg1 As T, arg2 As String)
        ClsObj.Method2(Of T)(arg1, arg2)
    End Sub

    Public Sub Method3(arg1 As T, arg2 As String, arg3 As Integer)
        ClsObj.Method3(Of T)(arg1, arg2, arg3)
    End Sub

    Public Function Function1(arg1 As T, arg2 As String) As String
        Return ClsObj.Function1(Of T)(arg1, arg2)
    End Function

    Public Function Function2(arg1 As T, arg2 As String) As List(Of T)
        Return ClsObj.Function2(Of T)(arg1, arg2)
    End Function

    Public Function Function3(arg1 As T, arg2 As String, arg3 As Integer) As Decimal
        Return ClsObj.Function3(Of T)(arg1, arg2, arg3)
    End Function
End Class