类似接口或派生 class 的泛型
Something like interface or derived class for generics
我的业务模型有两个实现,有点像下面的框架。
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
第一个实现是泛型 class,而第二个实现具有泛型方法。
我想确保两个 classes 应该有相同的属性和方法。
开发人员可能会在一个 class 中添加一个方法而忘记在另一个中添加,我想避免这种情况。随着 class 变大,很难手动确保这一点,因为方法在两个 class 中的顺序可能不同。
由于泛型定义的差异,一个接口不起作用(一个有 (Of T)
而另一个没有)。
类似派生的 class 概念不起作用,因为每个方法都有 2 个具有相同签名的定义。
有出路吗?
不,在 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
至少你只有这两个界面同步,但是,正如你已经知道的那样,仍然没有办法强制这两个界面保持同步。我能想到的唯一方法是添加一个单元测试,它使用反射来比较两个接口,并在它们不同时生成失败结果。至少这样您就可以使单元测试的 运行 自动化,以便在任何时候对一个而不是另一个进行更改时立即得到通知。
或者,由于两个 class 似乎做同样的事情,将两者合二为一可能更有意义 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
这仍然不会强制 class 拥有每种方法的两个版本,但它至少都在同一个 class 中,更明显的是,如果你改变一个你有改变另一个,特别是如果你添加一些评论到那个效果。
最后一个选择是使用 code-generation 工具(例如 T4 模板)从同一脚本自动生成两个 classes。这样,当您需要进行更改时,只需在脚本中更改一次,然后两个 classes 将自动重新生成以匹配。
感谢所有帮助我解决问题的人。我最终创建了一个 T4 模板。由于各种原因,建议的所有其他选项都不合适。
对于像我这种情况的任何人,这就是我所做的:
- 我的class(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)
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
我在名为 Class1OfT.tt
的项目中添加了一个 T4 模板,并设置了它的属性 Build Action = None
和 Custom Tool = TextTemplatingFileGenerator
将以下代码添加到我的 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
#>
- 保存文件时,将生成一个名为
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
我的业务模型有两个实现,有点像下面的框架。
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
第一个实现是泛型 class,而第二个实现具有泛型方法。
我想确保两个 classes 应该有相同的属性和方法。 开发人员可能会在一个 class 中添加一个方法而忘记在另一个中添加,我想避免这种情况。随着 class 变大,很难手动确保这一点,因为方法在两个 class 中的顺序可能不同。
由于泛型定义的差异,一个接口不起作用(一个有 (Of T)
而另一个没有)。
类似派生的 class 概念不起作用,因为每个方法都有 2 个具有相同签名的定义。
有出路吗?
不,在 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
至少你只有这两个界面同步,但是,正如你已经知道的那样,仍然没有办法强制这两个界面保持同步。我能想到的唯一方法是添加一个单元测试,它使用反射来比较两个接口,并在它们不同时生成失败结果。至少这样您就可以使单元测试的 运行 自动化,以便在任何时候对一个而不是另一个进行更改时立即得到通知。
或者,由于两个 class 似乎做同样的事情,将两者合二为一可能更有意义 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
这仍然不会强制 class 拥有每种方法的两个版本,但它至少都在同一个 class 中,更明显的是,如果你改变一个你有改变另一个,特别是如果你添加一些评论到那个效果。
最后一个选择是使用 code-generation 工具(例如 T4 模板)从同一脚本自动生成两个 classes。这样,当您需要进行更改时,只需在脚本中更改一次,然后两个 classes 将自动重新生成以匹配。
感谢所有帮助我解决问题的人。我最终创建了一个 T4 模板。由于各种原因,建议的所有其他选项都不合适。
对于像我这种情况的任何人,这就是我所做的:
- 我的class(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)
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
我在名为
Class1OfT.tt
的项目中添加了一个 T4 模板,并设置了它的属性Build Action = None
和Custom Tool = TextTemplatingFileGenerator
将以下代码添加到我的
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
#>
- 保存文件时,将生成一个名为
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