Get property by Name

  • VB.NET

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von Kagurame.

    Get property by Name

    Hallo,

    ich versuche gerade eine generische Validierung von Werten zu realisieren.

    In einem Konkreten Fall soll eine List(Of T) nach einem mitgegebenen String-Value suchen, der Name dieser Property ist aber vorher unklar (Es handelt sich hierbei um eine beliebige Property in einem Beliebigen mitgegebenen Typ).
    Der Name der zu überprüfenden Property soll per String mitgegeben werden können.

    Ich hatte schon öfters gelesen, das es irgendwo eine Methode GetPropertyByName gibt. Habe auch so etwas gefunden (IStereoType-Interface), aber msdn gibt so wenige Informationen darüber, das ich weder weis was es ist noch was es genau macht, es heißt nur, das es unter gewissen Umständen (unklar welche) nicht angewendet werden kann.

    Ich vermute, es gibt irgendwelche Wege über Reflection, wisst ihr da was?
    Oder würdet ihr gar sagen, das ich den Such-Algorythmus jeweils mit übergeben soll und dies einfacher wäre? (Stichwort Delegates / Lambdas)

    Naja, Danke im Voraus für Feedback / Tipps / Lösungsansätze.

    Visual Basic-Quellcode

    1. Public Class UniqueNameRule(Of T)
    2. Implements IRule(Of String) 'Interface, welches Validate und SoftRule bereitstellt
    3. Public Property SoftRule As Boolean Implements IRule(Of String).SoftRule
    4. Public Property List As List(Of T)
    5. Public Property PropertyName As String
    6. Public Sub New(list As List(Of T), propertyName As String)
    7. Me.List = list
    8. Me.PropertyName = propertyName
    9. End Sub
    10. Public Function Validate(valueToValidate As String) As RuleResult Implements IRule(Of String).Validate
    11. '#################################################
    12. ' Hier müsste es irgendwie rein
    13. Dim elements = From element As T In List Where {MeineProperty} = valueToValidate
    14. '#################################################
    15. Return New RuleResult(elements.Count < 0, SoftRule, "Message")
    16. End Function
    17. End Class
    Was viel einfachers:
    Gib Deinem Validierer eine Funktion mit, die eine Instanz vom angegebenen Typ entgegennimmt und einen String zurückgibt.

    VB.NET-Quellcode

    1. Private Sub Button1_Click() Handles Button1.Click
    2. Dim Instance1 As New DatClass With {.YourStringProperty = "Get:foo.bar"}
    3. Dim Instance2 As New DatClass With {.YourStringProperty = "Get:foo"}
    4. Dim MyChecker1 As New Checker(Of DatClass)(AddressOf Converter, Instance1, Instance2)
    5. MyChecker1.Check()
    6. 'Oder mit anonymer Funktion:
    7. Dim MyChecker2 As New Checker(Of DatClass)(Function(Target As DatClass) Target.YourStringProperty, Instance1, Instance2)
    8. MyChecker2.Check()
    9. End Sub
    10. Public Function Converter(ByVal Target As DatClass) As String
    11. Return Target.YourStringProperty
    12. End Function
    13. Class DatClass
    14. Public Property YourStringProperty As String
    15. End Class
    16. Class Checker(Of T)
    17. Dim Entries As New List(Of T)
    18. Dim Converter As Func(Of T, String)
    19. Public Sub New(ByVal NewConverter As Func(Of T, String), ByVal ParamArray NewEntries() As T)
    20. Converter = NewConverter
    21. Entries.AddRange(NewEntries)
    22. End Sub
    23. Public Sub Check()
    24. For Each i As T In Entries
    25. If Converter(i).Contains("Foo.Bar") Then
    26. End If
    27. Next
    28. End Sub
    29. End Class


    Würde ich da bevorzugen.
    Ob es schneller als Reflection ist habe ich nicht getestet, aber es wäre denkbar.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    @picoflop:
    Gut, dann geht's natürlich nicht.

    Aber

    Es handelt sich hierbei um eine beliebige Property in einem Beliebigen mitgegebenen Typ

    hat für mich jetzt nicht bedeutet, dass der Typ zur Entwurfszeit noch nicht bekannt ist.

    Alternativ könnte man nur Typen annehmen, die ein Interface implementieren.

    VB.NET-Quellcode

    1. Private Sub Button1_Click() Handles Button1.Click
    2. Dim Instance1 As New DatClass With {.YourStringProperty = "Get:foo.bar"}
    3. Dim Instance2 As New DatClass With {.YourStringProperty = "Get:foo"}
    4. Dim MyChecker As New Checker(Of DatClass)(Instance1, Instance2)
    5. MyChecker.Check()
    6. End Sub
    7. Interface IHasAProperty
    8. Function GetProperty() As String
    9. End Interface
    10. Class DatClass
    11. Implements IHasAProperty
    12. Public Property YourStringProperty As String
    13. Public Function GetProperty() As String Implements IHasAProperty.GetProperty
    14. Return YourStringProperty
    15. End Function
    16. End Class
    17. Class Checker(Of T As IHasAProperty)
    18. Dim Entries As New List(Of T)
    19. Public Sub New(ByVal ParamArray NewEntries() As T)
    20. Entries.AddRange(NewEntries)
    21. End Sub
    22. Public Sub Check()
    23. For Each i As T In Entries
    24. If i.GetProperty.Contains("Foo.Bar") Then
    25. End If
    26. Next
    27. End Sub
    28. End Class


    Das hat natürlich den Nachteil, dass man keine bereits vorgegebenen Klassen verwenden kann. Es sei denn man erstellt eine abgeleitete Klasse und lässt diese das Interface implementieren.


    Aber mir fällt jetzt wirklich kein Beispiel ein, wo es nicht möglich ist einen Konverter anzugeben.
    Ich könnte mir denken, dass das nötig ist, wenn man mit Reflection irgendeinen Typ ausliest. Aber dann kann man ja sowieso Reflection verwenden.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Hallo,

    bin erst heute dazu gekommen, mir das ganze mal genauer anzuschauen.

    @Niko Ortner: Wenn ich es so machen würde (was sicherlich möglich wäre), müsste ich - Nur um hier Validieren zu können, in einer anderen Assembly rumpfuschen(Welche Shared für dieses und daraus abgeleitete Projekte ist). Vielleicht verständlich, das ich das daher nicht unbedingt möchte.

    Das mit dem Erben wäre vielleicht möglich, hier bin ich in einem Vergleichbaren Fall wie du erwähnst, das ich keine Vorgefertigten Klassen nutzen könnte... Daher Erben evtl.

    @picoflop:

    Habe mir das Beispiel angesehen, aber nicht jede Klasse hat ein Invoke, oder irre ich mich da? Habe mit Reflection zu wenig Erfahrung, und verstanden habe ich das Beispiel auch nicht ganz...
    Habe es mir mal im Naming angepasst, damit ich es besser lesen kann (eben so wie ich es gewohnt bin, Casing und Namensgebung, was ich raus lesen konnte...), aber wirklich was gebracht hat es leider nicht.

    Ich habe bspw. einen Typ Member (selbst erstellt) oder Group (selbst erstellt), aber wie würde es da funktionieren, wenn ich bei Group den GroupName bräuchte oder bei Member die RoomNumber (beides String-Properties)?
    Kannst du da vielleicht einen Denkanstoß geben oder ein kleines Beispiel, das ich es vielleicht verstehe?


    Ich denke ich werde es mal mit Interface und Vererbung versuchen, bis ich den Weg über Reflection verstanden habe... ^^

    Kagurame schrieb:

    ich versuche gerade eine generische Validierung von Werten zu realisieren.

    In einem Konkreten Fall soll eine List(Of T) nach einem mitgegebenen String-Value suchen, der Name dieser Property ist aber vorher unklar (Es handelt sich hierbei um eine beliebige Property in einem Beliebigen mitgegebenen Typ).
    Der Name der zu überprüfenden Property soll per String mitgegeben werden können.
    ich glaub das wird nix.

    Aber ich versteh auch nicht, was "Validieren" mit "Suchen in einer List(Of T)" zu tun hat.
    Und nach einer Property suchen, deren Name unbekannt ist stößt sich iwie ein bischen mit meinen Vorstellungen von streng typisierter Programmiersprache und Zeugs.

    Was man allerdings generisch machen kann, dass man eine kleine ganze Methode mitgibt, mit der die Suche entscheidet, ob ein Item matcht oder nicht.
    Aber das ist dir vmtl. bereits bekannt, odr?

    ErfinderDesRades schrieb:

    Aber ich versteh auch nicht, was "Validieren" mit "Suchen in einer List(Of T)" zu tun hat.

    Indirekt.

    Ich habe verschiedene Regeln (Rule(Of T)), und jede Regel bekommt einen zu überprüfenden Wert mit. Um eine solche geht es hier konkret.
    Ein Validator stellt sicher (er validiert), das bei einem festgelegten Wert ein Satz von Regeln eingehalten wird (er kann eine beliebige Anzahl an Regeln, welche den selben Typ haben (Of T) Validieren und gibt ein Gesamtergebnis zurück).

    Beispielhafter Pseudocode

    GruppenName = "Obergeschoss"

    StringNotEmptyRegel(GruppenName) ' Typ String
    StringNotLongerThanRegel(GruppenName) ' Typ String, entsprechendes Feld in der DB hat nur 25 Characters Platz, ich überprüfe direkt bei der Eingabe, ohne zur DB rennen zu müssen
    StringNotExistRegel(GruppenName) ' Gruppenname darf nur einmal existieren, Liste von Gruppen wird überprüft

    Validator.ValidiereAlleRegeln(Regel, Regel, Regel) ' => Mitgegebener Wert zulässig => True / False


    Wie in dem (vielleicht nicht so guten) Beispiel ersichtlich, möchte ich bspw. wissen, ob eine Gruppe schon existiert. Ich habe eine Liste von Gruppen, die entsprechende Eigenschaft heißt GroupName.
    Bei einem Member (anderer Typ, andere List mit Member´s drinne) möchte ich vielleicht den LogOnName überprüfen, in einem anderen Fall vielleicht die PhoneNumber-Eigenschaft.

    Klar, das ich nicht für jede mögliche Eigenschaft, die irgendwo nicht doppelt existieren soll, eine eigene Regel schreiben möchte, sondern eine Intelligente Regel, der ich sage, wie die zu überprüfende Eigenschaft heißt.


    ErfinderDesRades schrieb:

    Was man allerdings generisch machen kann, dass man eine kleine ganze Methode mitgibt, mit der die Suche entscheidet, ob ein Item matcht oder nicht.

    Ein Member, oder eine Group, die nichts anderes soll, als Daten zu halten (nämlich Informationen über diese), hat aber nichts damit zu tun, ob sie irgendwo schonmal Ähnlichkeiten zu jemand anders hat.
    Damit fällt dieses Argument weg (oder ich verstehe dich falsch)

    Was ich nun gemacht habe, ist ein Interface, welches dem Typ die Möglichkeit bietet, jemand anderes die entsprechende Eigenschaft zu sagen.

    Zum Vergleich: Du weißt nicht, ob es irgendwo jemanden gibt, der genauso heißt wie du, dafür haben wir Ämter, die das vielleicht irgendwo wissen. Aber du kannst uns deinen Namen nennen, und jeder andere auch und wir können das selbst überprüfen. Dafür hätten wir ein Computer-Programm, aber dir fällt diese Aufgabe nicht zu, du hast andere Aufgaben (Computer-Technisch gesehen)

    Konnte ich so das Problem vielleicht verdeutlichen?
    mir ist dieser Regel-Kram im wesentlichen schon bekannt, und eine dieser fabelhaften Regeln heißt auch "DelegateRule", und der gibt man einen Delegaten, anhand dessen validiert wird.
    Verwendet man häufig für kompliziertere Regelungen.

    Wie gesagt: Da alle möglichen Properties anzugeben, zusammen mit ihren Match-Werten passt nicht in den Kontext einer strengtypisierten Sprache.

    Hingegen eine Berechnung anzugeben, die aussagt: valide oder nicht - dazu sind Delegaten erfunden worden.

    Ich versuchma im Ämter-Beispiel zu formulieren:
    propertymäßig würde ich hingehen und sagen: Sucht mal: Alter: 150, Name: "Rumpelstilzchen"

    Und dann finden die vlt. alle 150jährigen Rumpelstilzchen

    Delegate-mäßig binnich viel mächtiger, ich kann zb auch angeben, dass die RS älter als 150 sein sollen - also suchtma: Alter > 150 Andalso Name = "Rumpelstilzchen"
    (Keine Ahnung, warum ich dafür nicht zuständig sein sollte.)

    Da kriegst du propertymäßig schnell einen Vogel, weil es gibt ja einige mehr Vergleichsmöglichkeiten: >, >=, =, <, <=, Like, .StartsWith, .Contains, ...
    Das wird lustig, wenn du da 4 Properties mitnander verknüpfen willst - da wirst du einen Haufen Regel-Klassen coden müssen.

    Guck - hier habichmich mal mit so Zeugs auseinandergesetzt: IDataErrorInfo

    ErfinderDesRades schrieb:

    (Keine Ahnung, warum ich dafür nicht zuständig sein sollte.)

    Weil ein Member hier nicht wissen muss, ob er schonmal existiert oder ob es einen mit selben Namen gibt. Wenn das überprüft werden soll, dann sollte dies der tun, der ihn anlegt. Und wenn es heißt, einfach in den Raum "Hans" zu rufen und Hans fühlt sich angesprochen.
    Du solltest Hans aber nicht fragen können, wie viele Hans es gibt, er würde dir wahrscheinlich mit 'wes ich doch net' oder so antworten... aber du kannst nach Hans ausrufen und mehrere Hans fühlen sich angesprochen...

    Eine Klasse tut immer nur das, wofür sie da ist. Und wenn ein Member zum Member-Daten halten da ist, dann ist er nicht zum Redundazen suchen da... dafür gibt es irgendwo Listen oder Vergleichbares wo das drinne steht...
    Solltest du aber wissen... ^^

    OK, habs nu mit den Delegaten gemacht... bin vorher nicht auf die Idee oder ähnliche gekommen, funktioniert nun alles bestens...

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class DelegateWithListRule(Of T)
    2. Implements IRule(Of T)
    3. Public Property SoftRule As Boolean Implements IRule(Of T).SoftRule
    4. Public Delegate Function ArePropertiesEqual(element As T, elementToCompare As T) As Boolean
    5. Public Property [Delegate] As ArePropertiesEqual
    6. Public Elements As List(Of T)
    7. Sub New([delegate] As ArePropertiesEqual, elements As List(Of T))
    8. Me.Delegate = [delegate]
    9. Me.Elements = elements
    10. End Sub
    11. Public Function Validate(valueToValidate As T) As RuleResult Implements IRule(Of T).Validate
    12. Dim matchingElements = From element As T In Elements Where [Delegate].Invoke(element, valueToValidate)
    13. Return New RuleResult(matchingElements.Count = 0, SoftRule, "Wert darf nicht mehrfach gewählt werden.")
    14. End Function
    15. End Class
    16. '...
    17. Private _memberList As List(Of Member)
    18. Private _groupList As List(Of Group)
    19. <TestInitialize()>
    20. Public Sub Initialize()
    21. Dim memA As New Member With {.LogOnName = "MHans", .PhoneNumber = "12345"}
    22. Dim memB As New Member With {.LogOnName = "PBernd", .PhoneNumber = "12342"}
    23. Dim memC As New Member With {.LogOnName = "MMeier", .PhoneNumber = "12343"}
    24. _memberList = New List(Of Member)
    25. _memberList.AddRange({memA, memB, memC})
    26. Dim grpA As New Group With {.Name = "GrpA"}
    27. Dim grpB As New Group With {.Name = "GrpB"}
    28. Dim grpC As New Group With {.Name = "GrpC"}
    29. _groupList = New List(Of Group)
    30. _groupList.AddRange({grpA, grpB, grpC})
    31. End Sub
    32. Private Function CompareGroup(grp As Group, otherGrp As Group) As Boolean
    33. Return grp.Name.Equals(otherGrp.name)
    34. End Function
    35. Private Function CompareMember(mem As Member, otherMem As Member) As Boolean
    36. Return mem.LogOnName.Equals(otherMem.LogOnName) OrElse mem.PhoneNumber.Equals(otherMem.PhoneNumber)
    37. End Function
    38. <TestMethod()>
    39. Public Sub Validate_Succeeds_UniqueStringPropertyValueRule_CheckGroupName_NoWarning()
    40. Dim grp As New Group With {.Name = "grp"}
    41. Dim validatorResult As ValidatorResult = Validator.Validate(Of Group)(grp, {New DelegateWithListRule(Of Group)(AddressOf CompareGroup, _groupList) With {.SoftRule = False}})
    42. Assert.IsTrue(validatorResult.IsSuccessFull)
    43. Assert.AreEqual(0, validatorResult.MessageList.Count)
    44. End Sub
    45. '...
    46. <TestMethod()>
    47. Public Sub Validate_Succeeds_UniqueStringPropertyValueRule_CheckMemberProperties_Warning()
    48. Dim mem As New Member With {.LogOnName = "MHans", .PhoneNumber = "0"}
    49. Dim validatorResult As ValidatorResult = Validator.Validate(Of Member)(mem, {New DelegateWithListRule(Of Member)(AddressOf CompareMember, _memberList) With {.SoftRule = True}})
    50. Assert.IsTrue(validatorResult.IsSuccessFull)
    51. Assert.AreEqual(1, validatorResult.MessageList.Count)
    52. Assert.IsTrue(validatorResult.MessageList(0).StartsWith("Warnung: "))
    53. End Sub
    54. <TestMethod()>
    55. Public Sub Validate_Fails_UniqueStringPropertyValueRule_CheckMemberProperties()
    56. Dim mem As New Member With {.LogOnName = "KBoese", .PhoneNumber = "12345"}
    57. Dim validatorResult As ValidatorResult = Validator.Validate(Of Member)(mem, {New DelegateWithListRule(Of Member)(AddressOf CompareMember, _memberList) With {.SoftRule = False}})
    58. Assert.IsFalse(validatorResult.IsSuccessFull)
    59. Assert.AreEqual(1, validatorResult.MessageList.Count)
    60. Assert.IsTrue(validatorResult.MessageList(0).StartsWith("Fehler: "))
    61. End Sub
    62. '...

    Kagurame schrieb:

    Eine Klasse tut immer nur das, wofür sie da ist. Und wenn ein Member zum Member-Daten halten da ist, dann ist er nicht zum Redundazen suchen da... dafür gibt es irgendwo Listen oder Vergleichbares wo das drinne steht...

    Redundanzen suchen tun die Delegaten ja auch nicht. Sie bestimmen nur, ob ein anneres Objekt eine Redundanz wäre. Also ähnlich der Equals-Methode, und das darf ein Objekt schon aussagen, ob es findet, dasses einem anneren gleicht oder nicht.
    Und in diesem Fall ist der Delegat ja nichtmal im Objekt angesiedelt, sondern in einer Regel bezüglich dieses Objekt-Typen.

    ErfinderDesRades schrieb:

    dasses einem anneren gleicht oder nicht

    Das schon, aber sie weiß nicht, ob es irgendwo noch einen "Bruder" gibt...

    Naja, is ja jetzt auch Wurscht, ansonsten können wir das gerne per PN klären, wenn du magst (Hat ja nicht mehr wirklich was mim Thema zu tun, geht ja schon mehr wieder Richtung Grundsätze...)

    Redundanzen suchen: Konkreter Fall eben genannt... ^^

    Edit:

    Hab das ganze nochmal überarbeitet. Funktioniert immer noch, erfüllt nun aber eher seinen eigentlichen zweck und kann nicht mehr - wie davor - als Unsauberer Ersatz für alle Regeln der Validierung genutzt werden.
    Neuer Code

    VB.NET-Quellcode

    1. Option Infer On
    2. Option Explicit On
    3. Option Compare Binary
    4. Option Strict On
    5. Imports System.Reflection
    6. Imports <Namespace>.Validation
    7. Imports System.Collections.Generic
    8. Imports System.Linq
    9. Namespace <Namespace>.Client
    10. ''' <summary>
    11. ''' Diese Klasse gleicht anhand eines mitgegebenen Delegaten eine Instanz vom Typ TElement mit einer List(Of TElement) ab und stellt sicher,
    12. ''' das in dieser eine Property nicht schon mit dem mitgegebenen Wert (valueToValidate As TMember) enthalten ist.
    13. ''' </summary>
    14. ''' <typeparam name="TElement"></typeparam>
    15. ''' <remarks></remarks>
    16. Public Class UniqueMemberRule(Of TElement, TMember)
    17. Implements IRule(Of TMember)
    18. Public Property SoftRule As Boolean Implements IRule(Of TMember).SoftRule
    19. Public Property InstanceList As List(Of TElement)
    20. ''' <summary>
    21. ''' Gibt den entsprechenden Member zurück
    22. ''' </summary>
    23. ''' <value>Element, welches den member enthält</value>
    24. ''' <returns>Member aus dem Element</returns>
    25. ''' <remarks></remarks>
    26. Public Property Member As Func(Of TElement, TMember)
    27. Sub New(instances As List(Of TElement), member As Func(Of TElement, TMember))
    28. Me.Member = member
    29. Me.InstanceList = instances
    30. End Sub
    31. Public Function Validate(valueToValidate As TMember) As RuleResult Implements IRule(Of TMember).Validate
    32. ' Liste der entsprechenden Properties zusammen bauen
    33. Dim propertyList = From e In InstanceList Select Member.Invoke(e)
    34. Return New RuleResult(Not propertyList.Contains(valueToValidate), SoftRule, "Wert darf nicht mehrfach gewählt werden.")
    35. End Function
    36. End Class
    37. End Namespace
    38. '...
    39. <TestInitialize()>
    40. Public Sub Initialize()
    41. Dim memA As New Member With {.LogOnName = "MHans", .PhoneNumber = "12345"}
    42. Dim memB As New Member With {.LogOnName = "PBernd", .PhoneNumber = "12342"}
    43. Dim memC As New Member With {.LogOnName = "MMeier", .PhoneNumber = "12343"}
    44. _memberList = New List(Of Member)
    45. _memberList.AddRange({memA, memB, memC})
    46. Dim grpA As New Group With {.Name = "GrpA"}
    47. Dim grpB As New Group With {.Name = "GrpB"}
    48. Dim grpC As New Group With {.Name = "GrpC"}
    49. _groupList = New List(Of Group)
    50. _groupList.AddRange({grpA, grpB, grpC})
    51. End Sub
    52. <TestMethod()>
    53. Public Sub Validate_Succeeds_UniqueMemberRule_CheckGroupName_NoWarning()
    54. Dim grpName As String = "grp"
    55. Dim rule As New UniqueMemberRule(Of Group, String)(_groupList, Function(givenGrp As Group) givenGrp.Name) With {.SoftRule = False}
    56. Dim validatorResult As ValidatorResult = Validator.Validate(grpName, {rule})
    57. Assert.IsTrue(validatorResult.IsSuccessFull)
    58. Assert.AreEqual(0, validatorResult.MessageList.Count)
    59. End Sub

    Ich sage ihm nun praktisch, wie er an die Eigenschaft rankommt, so überprüft er (Validiert) er nun auch selbst und lässt dies nicht dritte übernehmen.
    Ist im Endeffekt auch angenehmer zu verwenden...

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Kagurame“ ()