新的,修訂的,更新的答案;也刪除了我的意見如下:
好吧,這是可以工作的東西,並沒有可怕的侵入性。首先,談談你一直在看什麼。
屬性爲類型或屬性等提供元數據。由於它被編譯到最終的程序集中,唯一的方法是通過反射。你不能只是添加一個屬性到某個東西,讓它神奇地做一些沒有一些代碼的地方來激活它的方法等。該屬性本身必須使用反射來確定它與哪個類型和屬性相關聯。在某些情況下,有一個完整的圖書館來支持這些活動。
你在幾天前看過的NET Range
比看起來要複雜得多。請注意,沒有Validate
或CheckValue
類型的方法,只是布爾型IsValid
!除了是一個網絡事物,它也似乎與數據綁定有關 - 還有一個RangeAttributeAdapter
,一個ValidationArttribute
(Range繼承自此)和ValidationContext
。 RangeAttribute
只是指定值的位置,並且有很多,而不僅僅是一個簡單的屬性。
其他的東西,如PostSharp是「織」 - 很簡單的使用(種),但他們重寫代碼注入數額是多少,包裝監視屬性的更改,並致電範圍驗證方法(S)。然後進行更多的反思,將驗證的數據發回給該屬性。
重點:你所看到的東西都不是只是屬性,還有更多的事情正在進行。
下面是一個RangeManager
,它不像韋弗那樣透明,但它更簡單。核心是一個範圍屬性,您可以指定有效的最小/最大值。但是也需要創建一個RangeManager
對象,它將完成尋找屬性的繁重工作,從setter方法轉換,找到有效範圍並進行測試。它掃描它被實例化的類型以查找所有相關屬性。
這是反射調用節儉。當實例化時,管理器查找所有標記的屬性並保存對實例的引用,以便每次設置屬性時都不會調用幾個新的反射方法。
如果沒有其他,它顯示了一些涉及什麼。
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
對我來說,唯一能讓它超越我的阻礙因素就卸載數據驗證而言ñ以外的物業和類是使用的範圍列在屬性上方。
Attributes
對它們裝飾的屬性一無所知。這是其他作出該連接,並且其他將需要使用反射來獲取Attribute
數據。
同樣,性質一無所知分配給他們,因爲Attributes
是用於編譯器或別的東西像串行元數據Attributes
。此其他還必須使用反射來建立兩層(類型方法和元數據)之間的連接。
最後,Something Else最終可能是重寫您的發射組件以提供範圍檢查服務的工具,或者是通過如上所示的方法提供服務的庫。
更透明的事情的障礙是沒有像PropertyChanged事件掛鉤(請參閱PropertyInfo)。
- 用於實現IComparable在
RangeCheck
[Fody(https://github.com/Fody/PropertyChanged)做這樣的事情類似之間。它在編譯時注入INotifyPropertyChanged代碼。隨時查看來源,看看是否可以幫助你。 – 2014-10-01 19:33:43