如何为Windows窗体创建范围属性?

时间:2014-10-01 18:44:54

标签: .net vb.net winforms types attributes

我想创建名为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}}

上面的属性代码将忽略不在范围内的值,我明白这是因为我没有评估任何内容,但我不知道该怎么做。

2 个答案:

答案 0 :(得分:3)

还有其他可用于.Net平台的AOP框架/库,Spring.net AOPKingAOPFluentAOPAfterthought,......等等。

以下是使用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.dllAfterthought.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比看上去要多得多。请注意,没有ValidateCheckValue类型方法,只有布尔IsValid!除了作为Web-Thing之外,它似乎也与数据绑定有关 - 还有RangeAttributeAdapterValidationArttribute(范围继承自此)和ValidationContextRangeAttribute只是指定值的地方,而且只是一个简单的属性,还有更多的东西。

其他事情,比如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)。

  • 实施IComparable以在RangeCheck
  • 中使用