我想创建名为RangeAttribute
的元数据属性,而不使用PostSharp
中的Public NotInheritable Class MyType
''' <summary>
''' Gets or sets the value.
''' </summary>
''' <value>The value.</value>
Public Property MyProperty As Integer
Get
Return Me._MyValue
End Get
Set(ByVal value As Integer)
If value < Me._MyValueMin Then
If Me._MyValueThrowRangeException Then
Throw New ArgumentOutOfRangeException("MyValue", Me._MyValueExceptionMessage)
End If
Me._MyValue = Me._MyValueMin
ElseIf value > Me._MyValueMax Then
If Me._MyValueThrowRangeException Then
Throw New ArgumentOutOfRangeException("MyValue", Me._MyValueExceptionMessage)
End If
Me._MyValue = Me._MyValueMax
Else
Me._MyValue = value
End If
End Set
End Property
Private _MyValue As Integer = 0I
Private _MyValueMin As Integer = 0I
Private _MyValueMax As Integer = 10I
Private _MyValueThrowRangeException As Boolean = True
Private _MyValueExceptionMessage As String = String.Format("The valid range is beetwen {0} and {1}",
Me._MyValueMin, Me._MyValueMax)
End Class
等外部工具,因为它需要付费版本的库。
我发现的唯一官方信息是this answer,但荒谬的是,该页面只解释了如何声明类和继承...没有更多,所以我不仅仅是迷失了。< / p>
我的目的是转换这段代码:
Public NotInheritable Class MyType
''' <summary>
''' Gets or sets the value.
''' Valid range is between 0 and 10.
''' </summary>
''' <value>The value.</value>
<RangeAttribute(0, 10, ThrowRangeException:=False, ExceptionMessage:="")>
Public Property MyProperty As Integer
End Class
进入可重用和简化的东西,如下:
setter
所以为了完成这个任务,我已经开始编写属性,但是由于文档或示例不足而未完成,那么我不知道如何在没有属性的情况下评估属性的<AttributeUsage(AttributeTargets.Property Or
AttributeTargets.Parameter Or
AttributeTargets.ReturnValue Or
AttributeTargets.Field,
AllowMultiple:=False)>
Public Class RangeAttribute : Inherits Attribute
''' <summary>
''' Indicates the Minimum range value.
''' </summary>
Public Minimum As Single
''' <summary>
''' Indicates the Maximum range value.
''' </summary>
Public Maximum As Single
''' <summary>
''' Determines whether to throw an exception when the value is not in range.
''' </summary>
Public ThrowRangeException As Boolean
''' <summary>
''' Indicates the exception message to show when the value is not in range.
''' </summary>
Public ExceptionMessage As String
''' <summary>
''' Initializes a new instance of the <see cref="RangeAttribute"/> class.
''' </summary>
''' <param name="Minimum">The minimum range value.</param>
''' <param name="Maximum">The maximum range value.</param>
Public Sub New(ByVal Minimum As Single,
ByVal Maximum As Single)
Me.New(Minimum, Maximum, ThrowRangeException:=False, ExceptionMessage:=String.Empty)
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="RangeAttribute"/> class.
''' </summary>
''' <param name="Minimum">The minimum range value.</param>
''' <param name="Maximum">The maximum range value.</param>
''' <param name="ThrowRangeException">
''' Determines whether to throw an exception when the value is not in range.
''' </param>
Public Sub New(ByVal Minimum As Single,
ByVal Maximum As Single,
ByVal ThrowRangeException As Boolean,
Optional ByVal ExceptionMessage As String = "")
Me.Minimum = Minimum
Me.Maximum = Maximum
Me.ThrowRangeException = ThrowRangeException
If Not String.IsNullOrEmpty(ExceptionMessage) Then
Me.ExceptionMessage = ExceptionMessage
Else
Me.ExceptionMessage = String.Format("The valid range is beetwen {0} and {1}", Minimum, Maximum)
End If
End Sub
End Class
中的值手动添加上面代码属性中的getter / setter:
{{1}}
上面的属性代码将忽略不在范围内的值,我明白这是因为我没有评估任何内容,但我不知道该怎么做。
答案 0 :(得分:3)
还有其他可用于.Net平台的AOP框架/库,Spring.net AOP
,KingAOP
,FluentAOP
,Afterthought
,......等等。
以下是使用Afterthought
的建议解决方案。
注意:我们可以根据用于拦截的技术,在编译时注入拦截代码的框架(编译时IL编织)和执行拦截代码的框架将AOP框架划分为两个主要类别在运行期间进行注射(运行时IL编织或动态IL编织)。 PostSharp支持当前版本的两种方法,每种技术都有自己的优点和缺点,超出了本答案的范围,有关详细信息,请参阅http://www.postsharp.net/aop.net
在此示例中,我们选择了基于Afterthought
框架的编译时IL-Weaving(Afterthought
仅支持编译时IL编织)
<强> 1-Prepration 强>
你可以从https://github.com/r1pper/Afterthought/releases获得Afterthought(你可以下载二进制文件,或者你可以自己编译源代码,我可以在这里使用二进制路径)
提取包有2个文件Afterthought.dll
和Afterthought.Amender.exe
,引用afterthought.dll
。
正如我之前所说,Afterthought使用编译时IL编织,这正是Afterthought.Amender.exe
所做的。
我们应该在每次构建之后调用Amender,将拦截代码注入我们的程序集:
Afterthought.Amender.exe“assembly”
我们可以通过为我们的项目定义一个新的Post Build event
来自动完成任务(这正是 PostSharp 所做的)这里我在项目的目录中复制了Afterthought文件夹,这是我的帖子构建事件(您可能需要根据文件夹位置更改发布事件):
“$(ProjectDir)Afterthought \ Afterthought.Amender.exe”“$(TargetPath)”
好的,现在我们已经准备好编写代码了
2-带有范围控制的示例代码,用于[0,10]
之间的整数在此示例中,我们定义了一个范围控制属性并将其命名为RangeAttribute
,试图拦截属性setter方法以检查我们的设置值是否在该范围内。
拦截代码和注射:
Imports Afterthought
Imports System.Reflection
Public Class RangeAmendment(Of T)
Inherits Amendment(Of T, T)
Public Sub New()
MyBase.New()
Console.WriteLine("Injecting range check here!")
Properties.AfterSet(Sub(instance As T, pName As String, pvOld As Object, pv As Object, pvNew As Object)
Dim p As PropertyInfo = instance.GetType().GetProperty(pName)
Dim att As RangeAttribute = p.GetCustomAttribute(Of RangeAttribute)()
If att Is Nothing Then Return
Dim v As Object = p.GetValue(instance)
Dim castedValue As Integer = Convert.ToInt32(v)
If (castedValue < att.Min OrElse castedValue > att.Max) Then
Throw New RangeException(p.Name, att.Min, att.Max)
End If
End Sub)
End Sub
End Class
课程和定义:
Public Class RangeAttribute
Inherits Attribute
Public Property Max As Integer
Public Property Min As Integer
Public Sub New(ByVal min As Integer, ByVal max As Integer)
MyBase.New()
Me.Min = min
Me.Max = max
End Sub
End Class
Public Class RangeException
Inherits ApplicationException
Public Sub New(ByVal propertyName As String, ByVal min As Integer, ByVal max As Integer)
MyBase.New(String.Format("property '{0}' value should be between [{1},{2}]", propertyName, min, max))
End Sub
End Class
<Amendment(GetType(RangeAmendment(Of )))>
Public Class TestClass
<Range(0, 10)>
Public Property Value As Integer
Public Sub New()
MyBase.New()
End Sub
End Class
<强>示例:强>
Module Module1
Sub Main()
Dim test = New TestClass()
Try
Console.WriteLine("try setting value to 5")
test.Value = 5
Console.WriteLine(test.Value)
Console.WriteLine("try setting value to 20")
test.Value = 20
Console.WriteLine(test.Value)
Catch ex As RangeException
Console.WriteLine(ex.Message)
End Try
Console.ReadKey()
End Sub
End Module
现在,在构建项目时,您应该在构建输出中看到类似的消息:
在这里注射范围检查!
修改AopVb3.exe(3.685秒)
==========重建全部:1成功,0失败,0跳过==========
并且控制台的输出应为:
尝试将值设置为5
5
尝试将值设置为20
属性“值”值应介于[0,10]
之间
答案 1 :(得分:1)
新的,修订的,更新的答案;还删除了我的评论:
好的,这是可以工作的东西,并不是非常侵入性的。首先,谈谈你一直在看的内容。
属性为类型或属性等提供元数据。由于它被编译到最终的程序集中,唯一的方法是通过Reflection。你不能只是添加一个属性,并让它神奇地做一些没有一些代码的东西,以激活其中的方法等。然后,属性本身必须使用Reflection来确定与之关联的Type和Property。在某些情况下,有一个完整的库来支持这些活动。
几天前你看过的网Range
比看上去要多得多。请注意,没有Validate
或CheckValue
类型方法,只有布尔IsValid
!除了作为Web-Thing之外,它似乎也与数据绑定有关 - 还有RangeAttributeAdapter
,ValidationArttribute
(范围继承自此)和ValidationContext
。 RangeAttribute
只是指定值的地方,而且只是一个简单的属性,还有更多的东西。
其他事情,比如PostSharp&#34; Weavers&#34; - 简单到使用(种类),但是他们重写代码以注入包装器的数量来监视属性更改并调用范围验证方法。然后更多反射将验证的数据发布回属性。
关键点:你所看到的一切都不是只是属性,还有更多的事情发生。
以下是RangeManager
,它不像Weaver那样透明,但更简单。核心是一个范围属性,您可以在其中指定有效的最小值/最大值。但是你还需要创建一个RangeManager
对象来完成查找属性,从setter方法转换,找到有效范围然后测试它。它会搜索实例中的类型以查找所有相关属性。
反思电话很节俭。当实例化时,管理器会找到所有标记的属性并保存对RangeAttribute
实例的引用,这样每次设置属性时都不会调用几个新的Reflection方法。
如果没有别的,它会显示一些涉及的内容。
Imports System.Reflection
Imports System.Globalization
Public Class RangeManager
<AttributeUsage(AttributeTargets.Property)>
Public Class RangerAttribute
Inherits Attribute
Public Property Minimum As Object
Public Property Maximum As Object
Private min As IComparable
Private max As IComparable
' converter: used by IsValid which is not overloaded
Private Property Conversion() As Func(Of Object, Object)
Public Property VarType As Type
Public Sub New(n As Integer, x As Integer)
Minimum = n
Maximum = x
VarType = GetType(Integer)
min = CType(Minimum, IComparable)
max = CType(Maximum, IComparable)
Conversion = Function(v) Convert.ToInt32(v,
CultureInfo.InvariantCulture)
End Sub
Public Sub New(n As Single, x As Single)
Minimum = n
Maximum = x
VarType = GetType(Single)
min = CType(Minimum, IComparable)
max = CType(Maximum, IComparable)
Conversion = Function(v) Convert.ToSingle(v,
CultureInfo.InvariantCulture)
End Sub
Public Sub New(n As Double, x As Double)
Minimum = n
Maximum = x
VarType = GetType(Double)
min = CType(Minimum, IComparable)
max = CType(Maximum, IComparable)
Conversion = Function(v) Convert.ToDouble(v,
CultureInfo.InvariantCulture)
End Sub
' overridable so you can inherit and provide more complex tests
' e.g. String version might enforce Casing or Length
Public Overridable Function RangeCheck(value As Integer) As Integer
If min.CompareTo(value) < 0 Then Return CInt(Minimum)
If max.CompareTo(value) > 0 Then Return CInt(Maximum)
Return value
End Function
Public Overridable Function RangeCheck(value As Single) As Single
If min.CompareTo(value) < 0 Then Return CSng(Minimum)
If max.CompareTo(value) > 0 Then Return CSng(Maximum)
Return value
End Function
Public Overridable Function RangeCheck(value As Double) As Double
If min.CompareTo(value) < 0 Then Return CDbl(Minimum)
If max.CompareTo(value) > 0 Then Return CDbl(Maximum)
Return value
End Function
' rather than throw exceptions, provide an IsValid method
' lifted from MS Ref Src
Public Function IsValid(value As Object) As Boolean
' dont know the type
Dim converted As Object
Try
converted = Me.Conversion(value)
Catch ex As InvalidCastException
Return False
Catch ex As NotSupportedException
Return False
' ToDo: add more Catches as you encounter and identify them
End Try
Dim min As IComparable = CType(Minimum, IComparable)
Dim max As IComparable = CType(Maximum, IComparable)
Return min.CompareTo(converted) <= 0 AndAlso
max.CompareTo(converted) >= 0
End Function
End Class
' map of prop names to setter method names
Private Class PropMap
Public Property Name As String ' not critical - debug aide
Public Property Setter As String
' store attribute instance to minimize reflection
Public Property Range As RangerAttribute
Public Sub New(pName As String, pSet As String, r As RangerAttribute)
Name = pName
Setter = pSet
Range = r
End Sub
End Class
Private myType As Type ' not as useful as I'd hoped
Private pList As List(Of PropMap)
Public Sub New()
' capture calling Type so it does not need to be specified
Dim frame As New StackFrame(1)
myType = frame.GetMethod.DeclaringType
' create a list of Props and their setter names
pList = New List(Of PropMap)
BuildPropMap()
End Sub
Private Sub BuildPropMap()
' when called from a prop setter, StackFrame reports
' the setter name, so map these to the prop name
Dim pi() As PropertyInfo = myType.GetProperties
For Each p As PropertyInfo In pi
' see if this prop has our attr
Dim attr() As RangerAttribute =
DirectCast(p.GetCustomAttributes(GetType(RangerAttribute), True),
RangerAttribute())
If attr.Count > 0 Then
' find it
For n As Integer = 0 To attr.Count - 1
If attr(n).GetType = GetType(RangerAttribute) Then
pList.Add(New PropMap(p.Name, p.GetSetMethod.Name, attr(n)))
Exit For
End If
Next
End If
Next
End Sub
' can be invoked only from Setter!
Public Function IsValid(value As Object) As Boolean
Dim frame As New StackFrame(1)
Dim pm As PropMap = GetPropMapItem(frame.GetMethod.Name)
Return pm.Range.IsValid(value)
End Function
' validate and force value to a range
Public Function CheckValue(value As Integer) As Integer
Dim frame As New StackFrame(1)
Dim pm As PropMap = GetPropMapItem(frame.GetMethod.Name)
If pm IsNot Nothing Then
Return pm.Range.CheckValue(value)
Else
Return value ' or something else
End If
End Function
' other types omitted for brevity:
Public Function CheckValue(value As Double) As Double
...
End Function
Public Function CheckValue(value As Single) As Single
...
End Function
Private Function GetPropMapItem(setterName As String) As PropMap
For Each p As PropMap In pList
If p.Setter = setterName Then
Return p
End If
Next
Return Nothing
End Function
End Class
如代码注释中所述,您可以继承RangerAttribute
,以便提供更广泛的范围测试。
样本用法:
Imports RangeManager
Public Class FooBar
Public Property Name As String
Private _IntVal As Integer
<Ranger(1, 10)>
Public Property IntValue As Integer
Get
Return _IntVal
End Get
Set(value As Integer)
_IntVal = rm.CheckValue(value)
End Set
End Property
' this is a valid place to use Literal type characters
' to make sure the correct Type is identified
Private _sngVal As Single
<Ranger(3.01F, 4.51F)>
Public Property SngValue As Single
Get
Return _sngVal
End Get
Set(value As Single)
If rm.IsValid(value) = False Then
Console.Beep()
End If
_sngVal = rm.CheckValue(value)
End Set
End Property
Private rm As RangeManager
Public Sub New(sName As String, nVal As Integer, dVal As Decimal)
' rm is mainly used where you want to validate values
rm = New RangeManager
' test if this can be used in the ctor
Name = sName
IntValue = nVal * 100
DblValue = dVal
End Sub
End Class
测试代码:
Dim f As New FooBar("ziggy", 1, 3.14)
f.IntValue = 900
Console.WriteLine("val tried: {0} result: {1}", 900.ToString, f.IntValue.ToString)
f.IntValue = -23
Console.WriteLine("val tried: {0} result: {1}", (-23).ToString, f.IntValue.ToString)
f.SngValue = 98.6
Console.WriteLine("val tried: {0} result: {1}", (98.6).ToString, f.SngValue.ToString)
你有它:基于属性的范围验证器的220行代码替换你的setter中的以下内容:
If value < Minimum Then value = Minimum
If value > Maximum Then value = Maximum
对我而言,只要将数据验证卸载到属性和类之外的东西,唯一让它超过我的gag因素的是使用的范围就在属性上方列出。< / p>
Attributes
对他们装饰的属性一无所知。由 Something Else 来建立连接, Something Else 需要使用Reflection来获取Attribute
数据。
同样,属性对分配给它们的Attributes
一无所知,因为Attributes
是用于编译器的元数据,或者 Something Else ,如序列化程序。此 Something Else 还必须使用Reflection在两个层之间建立连接(类型方法和元数据)。
最后, Something Else 最终会成为重写发出的程序集以提供范围检查服务的工具,或者是通过上述方法提供服务的库。
更透明的障碍是没有像PropertyChanged事件那样挂钩(参见PropertyInfo)。
RangeCheck