Felder ohne Reflection auslesen

  • C#

Es gibt 29 Antworten in diesem Thema. Der letzte Beitrag () ist von LaMiy.

    Felder ohne Reflection auslesen

    Ich versuche mein Vorhaben mal durch ein Beispiel zu erläutern.
    Ich habe eine Klasse Transform.
    Diese hat verschiedene Properties. Unter anderem auch Position, und LocalPosition vom Typ Vector3.

    Ich suche nun nach einer Möglichkeit alle Namen der Properties vom Typ Vector3 auszulesen. Außerdem möchte ich die Properties über den Namen lesen und schreiben können.
    Das ganze möchte ich jedoch ohne Reflection lösen.

    Ich habe mir als Alternative überlegt das ganze in einem Dictionary<string, Vector3> zu speichern. So kann ich alle Namen der Properties auslesen (Keys) und auch die Werte setzten (Values)
    Problem dabei ist allerdings, dass ich dann theoretisch für jeden Typ ein eigenes Dictionary brauchen würde.

    Ideal wäre es, wenn ich das irgendwie so aufrufen könnte ​string[] names = GetNames(typeof(Vector3)); (Oder generisch)
    Und dass es intern irgendwie einfacher verwaltet wird.

    Hat da vielleicht einer von euch Profis eine Idee wie man das machen könnte?
    Wäre sehr geil wenn da jemand was hätte :thumbup:

    Grüße

    Hier noch meine Beispielimplementierung

    C#-Quellcode

    1. ​public class Transform
    2. {
    3. public Dictionary<string, Vector3> vectors = new Dictionary<string, Vector3>();
    4. public Transform()
    5. {
    6. vectors.Add("position", new Vector3());
    7. vectors.Add("localPosition", new Vector3());
    8. }
    9. public Vector3 GetVector3(string name)
    10. {
    11. return vectors[name];
    12. }
    13. // ...
    14. private Vector3 _position;
    15. public Vector3 Position
    16. {
    17. get
    18. {
    19. return _position;
    20. }
    21. set
    22. {
    23. _position = value;
    24. vectors["position"] = _position;
    25. }
    26. }
    27. private Vector3 _localPosition;
    28. public Vector3 LocalPosition
    29. {
    30. get
    31. {
    32. return _localPosition;
    33. }
    34. set
    35. {
    36. _localPosition = value;
    37. vectors["LocalPosition"] = _position;
    38. }
    39. }
    40. // ...
    41. }
    Die Variante mit dem Dictionary wird auch in WPF eingesetzt. Neben Reflection gibts es keine andere Möglichkeit, die Member eines Typs aufzulisten.
    In WPF ist es so gelöst: eine Basisklasse definiert ein Dictionary<string, object> und stellt jeweils eine Methode zum typisierten setzen und lesen eines Eintrags. Ließe sich in einer eigenen Implementierung ganz leicht mit generischen Funktionen umsetzen.
    @Artentus Klar, genial! :)
    Meinst du so in etwa?

    C#-Quellcode

    1. ​public class Transform
    2. {
    3. public Dictionary<string, object> values = new Dictionary<string, object>();
    4. public Transform()
    5. {
    6. values.Add("position", new Vector3());
    7. values.Add("localPosition", new Vector3());
    8. }
    9. public T GetValue<T>(string name)
    10. {
    11. return (T) values[name];
    12. }
    13. // ...
    14. private Vector3 _position;
    15. public Vector3 Position
    16. {
    17. get
    18. {
    19. return _position;
    20. }
    21. set
    22. {
    23. _position = value;
    24. values["position"] = _position;
    25. }
    26. }
    naja - da hast du aber redundante Datenhaltung - einerseits ein normales BackingField, und das Dictionary ist ein zweites "universales" BackingField. Da solltest du die Standard-BackingFields weglassen, denn Redundanzen sind prinzipiell eine der gefährlichsten Fehler-Ursachen.

    C#-Quellcode

    1. public class Transform
    2. {
    3. public Dictionary<string, object> values = new Dictionary<string, object>();
    4. public Transform()
    5. {
    6. values.Add("position", new Vector3());
    7. values.Add("localPosition", new Vector3());
    8. }
    9. public T GetValue<T>(string name)
    10. {
    11. return (T) values[name];
    12. }
    13. // ...
    14. public Vector3 Position
    15. {
    16. get
    17. {
    18. return (Vector3) values["position"];
    19. }
    20. set
    21. {
    22. values["position"] = _position;
    23. }
    24. }
    (Nach diesem Prinzip funktioniert übrigens auch eine typisierte DataRow - nur DataRow hat einen eingeschränkten Satz an Datentypen verfügbar.)
    @ErfinderDesRades Jo stimmt. In meinem Fall ist das ein bisschen anders.
    Die Klasse MyTransform hat in diesem Fall keine eigenen Felder, sondern nur eine Property der Klasse Transform.
    Die restlichen Properties der Klasse Transform müssen dann manuell gesetzt werden.

    C#-Quellcode

    1. public class MyTransform : SharedBase
    2. {
    3. public Transform Value { get { return mValue; } set { mValue = value; } }
    4. private Transform mValue;
    5. public MyTransform()
    6. {
    7. values.Add("position", mValue.position);
    8. }
    9. // ...
    10. public Vector3 Position
    11. {
    12. get
    13. {
    14. return this.Value.position;
    15. return this.Get<Vector3>("position");
    16. }
    17. set
    18. {
    19. this.Value.position = value;
    20. this.Set<Vector3>("position", value);
    21. }
    22. }
    23. }


    Ich bin mir auch gerade irgendwie unschlüssig welchen Wert ich dann bei den Gettern und Settern zurückgeben, bzw. setzten muss.
    Weiß das jemand von euch?
    Ich hatte eigentlich vor eine Basisklasse dafür zu haben.
    Ich möchte jedoch eigentlich nur über den Namen auf die Property des gespeicherten Elements zugreifen.
    Das ist wie als wenn ich sage
    "position" gehört zu Position
    "localPosition" gehört zu LocalPosition

    Hat jemand eine Idee wie ich das dann konret machen muss?
    Ich erstelle mit meiner Klasse ja eigentlich nur eine Verknüpfung zu der anderen Klasse.

    Basisklasse

    C#-Quellcode

    1. /// <summary>
    2. /// Represents a class to access properties from name.
    3. /// </summary>
    4. public class SharedBase
    5. {
    6. // Saves the values
    7. public Dictionary<string, object> values = new Dictionary<string, object>();
    8. /// <summary>
    9. /// Gets the object with the given name
    10. /// </summary>
    11. /// <typeparam name="T">Type of the object</typeparam>
    12. /// <param name="name">Name of the object</param>
    13. /// <returns></returns>
    14. public T Get<T>(string name)
    15. {
    16. if (values.ContainsKey(name))
    17. {
    18. return (T)values[name];
    19. }
    20. else
    21. return default(T);
    22. }
    23. /// <summary>
    24. /// Sets the object with the given name.
    25. /// </summary>
    26. /// <typeparam name="T">Type of the object</typeparam>
    27. /// <param name="name">The name of the object</param>
    28. /// <param name="obj">The object</param>
    29. public void Set<T>(string name, T obj)
    30. {
    31. if (values.ContainsKey(name))
    32. values[name] = obj;
    33. }
    34. /// <summary>
    35. /// Get the names of all properties with the given type.
    36. /// </summary>
    37. /// <typeparam name="T">The typ the properties should be</typeparam>
    38. /// <returns>List of names</returns>
    39. public List<string> GetProperties<T>()
    40. {
    41. List<string> result = new List<string>();
    42. foreach (KeyValuePair<string, object> pair in values)
    43. {
    44. if (pair.Value.GetType() == typeof(T))
    45. {
    46. result.Add(pair.Key);
    47. }
    48. }
    49. return result;
    50. }
    51. }

    also hast du ein komplexes BackingField - vom Datentyp Transform.

    Ich verstehe nicht den Sinn, dass du auf die Transform-Props via String-Schlüssel zugreifen willst, aber eine redundanzfreie Möglichkeit wäre ein statisches Dictionary, was Getter-Delegaten bereitstellt.

    VB.NET-Quellcode

    1. public class MyTransform : SharedBase {
    2. public Transform Value { get; set; }
    3. private static Dictionary<string, Func<MyTransform, object>> _Getters = new Dictionary<string, Func<MyTransform, object>>();
    4. static MyTransform() {
    5. _Getters.Add("position", t=>t.Position);
    6. }
    7. public object this[string key] {
    8. get { return _Getters[key](this); }
    9. }
    10. // ...
    11. public Vector3 Position {
    12. get {
    13. return this.Value.Position;
    14. }
    15. set {
    16. this.Value.position = value;
    17. }
    18. }
    19. }
    20. //Aufruf:
    21. MyTransform aMytrans = new MyTransform();
    22. object aValue=aMytrans["position"];


    Setter analog.
    @ErfinderDesRades
    Ui, das muss ich erstmal verdauen ;)
    Wieso eigentlich static frage ich mich gerade irgendwie?

    Ja das ist, weil das Anwendungseispiel in Unity (Spiele IDE) ist.
    Dort soll man per Dropdown eine Variable ansprechen können und danach eine seiner Member. (ohne Reflection)
    Der ausgewählte Member soll dann abrufbar sein.
    bei static brauchts nur ein einziges Dictionary, und das wird nur ein einziges Mal initialisiert. Es enthält keine Property-Werte, sondern eben die Getter, mit denen die Property-Werte erst abgerufen werden (was redundanzfreie Aktualität garantiert).
    So kann man die verschiedenen Werte vieler MyTransform-Objekte abrufen - redundanzfrei.

    Umgesetzt ists mit einem Indexer (in VB vergleichbar einer Default - Property) - vmtl. ist dir das am ungewöhnlichsten.
    Kann man auch mit klassischer indizierter Property umsetzen, aber ich find einen Indexer hier nicht ganz unpassend.

    Übrigens auf meinem System hat die Transform-Klasse überhaupt keine Position-Property :P


    LaMiy schrieb:

    Dort soll man per Dropdown eine Variable ansprechen können und danach eine seiner Member. (ohne Reflection)
    Der ausgewählte Member soll dann abrufbar sein.
    klingt in meinen Ohren wirklich ziemlich nutzlos, zumindest im Produktiv-Einsatz.


    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Ich bin noch nicht ganz hinter das System von dir gekommen. Ich habe mal versucht dein Konzept als Basisklasse zu imlementieren.
    Spoiler anzeigen

    C#-Quellcode

    1. /// <summary>
    2. /// Represents a class to access properties from name.
    3. /// </summary>
    4. public class SharedBase : SharedVariable
    5. {
    6. // Saves the values
    7. public static Dictionary<string, System.Func<object, object>> _getters = new Dictionary<string, System.Func<object, object>>();
    8. /// <summary>
    9. /// Gets the object with the given name
    10. /// </summary>
    11. /// <typeparam name="T">Type of the object</typeparam>
    12. /// <param name="name">Name of the object</param>
    13. /// <returns></returns>
    14. public T Get<T>(string name)
    15. {
    16. if (_getters.ContainsKey(name))
    17. return (T) _getters[name](this);
    18. else
    19. return default(T);
    20. }
    21. /// <summary>
    22. /// Sets the object with the given name.
    23. /// </summary>
    24. /// <typeparam name="T">Type of the object</typeparam>
    25. /// <param name="name">The name of the object</param>
    26. /// <param name="obj">The object</param>
    27. //public void Set<T>(string name, T obj)
    28. //{
    29. // if (values.ContainsKey(name))
    30. // values[name] = obj;
    31. //}
    32. /// <summary>
    33. /// Get the names of all properties with the given type.
    34. /// </summary>
    35. /// <typeparam name="T">The typ the properties should be</typeparam>
    36. /// <returns>List of names</returns>
    37. public List<string> GetProperties<T>()
    38. {
    39. List<string> result = new List<string>();
    40. foreach (var pair in _getters)
    41. {
    42. if (pair.Value(this).GetType() == typeof(T))
    43. {
    44. result.Add(pair.Key);
    45. }
    46. }
    47. return result;
    48. }


    Entsprechende Unterklasse.

    C#-Quellcode

    1. [System.Serializable]
    2. public class SharedTransform : SharedBase
    3. {
    4. public Transform Value { get { return mValue; } set { mValue = value; } }
    5. [SerializeField]
    6. private Transform mValue;
    7. public SharedTransform()
    8. {
    9. _getters.Add("position", Transform => Value.position);
    10. _getters.Add("localPosition", Transform => Value.localPosition);
    11. _getters.Add("rotation", Quaternion => Value.rotation);
    12. }
    13. public Vector3 Position
    14. {
    15. get { return this.Value.position; }
    16. set { this.Value.position = value; }
    17. }


    Bekomme aber in Zeile 10 eine NullReference, ist ja auch logisch, aber was muss dann dahin?

    ErfinderDesRades schrieb:

    Übrigens auf meinem System hat die Transform-Klasse überhaupt keine Position-Property
    Ja das liegt daran, dass ich gerade in Unity rumbastel.

    ErfinderDesRades schrieb:

    klingt in meinen Ohren wirklich ziemlich nutzlos, zumindest im Produktiv-Einsatz.
    Ja das mag so erscheinen. In der Praxis geht es um eine visuelle Erweiterung für Unity mit dessen Hilfe man soz. Verhalten von bestimmten Spielobjekten erstellen kann.



    Hier geht es zum Beispiel darum, dass eine bestimmte Position gesetzt wird, wenn das aktuelle Objekt mit einem anderen zusammenstößt.
    Bei SetPosition kann man nun eine Variable vom Typ Vector3 anbinden.


    Blöd ist nur, dass man hier nur direkt Variablen vom Typ Vector3 auswählen kann.
    Ich möchte es hinbekommen, dass man auch Transform.Position auswählen kann, was ja auch vom Typ Vector3 ist. Da Transform aber vom Typ Transform ist geht das nicht.

    Ich hoffe ich konnte es einigermaßen erkläaren :)

    LaMiy schrieb:

    Das ganze möchte ich jedoch ohne Reflection lösen.
    Warum eigentlich?
    Ich hab mal einen Makrointerpreter gesehen, da wurden die Properties per Reflection befüllt und überladene Prozeduren aufgerufen.
    Jede "Elementar-Unit" der Makro-Source war eine Klasse mit gemeinsamer Basisklasse, beliebigen Properties und sehr wenigen Prozeduren:
    - Konstruktor
    - Init
    - DoIt
    - Exit
    in der Klasse selbst
    und Dump und Co in der Basisklasse.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    LaMiy schrieb:

    Bekomme aber in Zeile 10 eine NullReference, ist ja auch logisch, aber was muss dann dahin?
    initialisiere das Dictionary richtig:

    C#-Quellcode

    1. public SharedTransform()
    2. {
    3. _getters.Add("position", sb => sb.Value.position);
    4. _getters.Add("localPosition", sb => sb.Value.localPosition);
    5. _getters.Add("rotation", sb => sb.Value.rotation);
    6. }
    Hilfreich wäre hier eine korrekte typisierung, das hilft auch dem Programmierer, Äpfel von Birnen zu unterscheiden:

    C#-Quellcode

    1. public class SharedBase : SharedVariable
    2. {
    3. // Saves the values <- bitte auf solche irreführenden Kommentare verzichten!
    4. public static Dictionary<string, System.Func<SharedBase, object>> _getters = new Dictionary<string, System.Func<SharedBase, object>>();

    @ErfinderDesRades
    Ah danke dir. Beeindruckend.
    Das mit dem Kommentar ist noch von einem meiner letzten Tests. Klar gehört das da so nicht hin.

    sb => sb.Value.position
    Kann aber ja nicht funktionieren, denn SharedBase hat keine Property Value.
    Ich habe es mal so abgeändert.

    C#-Quellcode

    1. _getters.Add ("position", delegate (SharedBase sb) {
    2. SharedTransform trans = (SharedTransform) sb;
    3. return trans.Position;
    4. });

    Klappt. Gibt es dafür auch so eine schicke Kurzschreibweise?
    Ich bräuchte nochmal Hilfe.
    Ich versuche nun doch ein bisschen Reflection zu verwenden. Und zwar um die Namen aller Properties auszulesen. (Damit ich nicht manuell die Properties erstellen muss).
    Das Auslesen der Namen klappt wunderbar, jedoch hadert es mit den Werten etwas.

    C#-Quellcode

    1. public void Init()
    2. {
    3. PropertyInfo[] infos = typeof(Transform).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    4. foreach (PropertyInfo info in infos)
    5. {
    6. Debug.Log(info.Name);
    7. //_getters.Add(info.Name, sb => info.GetValue(((SharedTransform1) sb).Value, null));
    8. _getters.Add (info.Name, delegate (SharedBase sb) {
    9. SharedTransform1 trans = (SharedTransform1) sb;
    10. return info.GetValue(trans.Value, null);
    11. });
    12. }
    13. Debug.Log(_getters["position"](this));
    14. }


    So habe ich es probiert. Will aber nicht klappen. (Values sind null, wenn ich sie über _getters["position"](this) abrufen will)
    return info.GetValue(trans.Value, null); macht mir dabei ein bisschen Bauchschmerzen. Ich weiß nicht ob das so korrekt ist.


    Nochmal zur Erklärung. SharedTransform1 ist das gleich wie SharedTransform, nur eine Kopie, damit das Original nicht verletzt wird.
    SharedTransform1 hat Value vom Typ Transform als Property. An die Member von Value soll dann das Dictionary gebunden werden.
    Hier nochmal die Implementation beider Klassen.

    Spoiler anzeigen

    C#-Quellcode

    1. ​using System.Collections.Generic;
    2. using BehaviorDesigner.Runtime;
    3. /// <summary>
    4. /// Represents a class to access properties from name.
    5. /// </summary>
    6. public class SharedBase : SharedVariable
    7. {
    8. public static Dictionary<string, System.Func<SharedBase, object>> _getters = new Dictionary<string, System.Func<SharedBase, object>>();
    9. /// <summary>
    10. /// Gets the object with the given name
    11. /// </summary>
    12. /// <typeparam name="T">Type of the object</typeparam>
    13. /// <param name="name">Name of the object</param>
    14. /// <returns></returns>
    15. public T Get<T>(string name)
    16. {
    17. if (_getters.ContainsKey(name))
    18. return (T) _getters[name](this);
    19. else
    20. return default(T);
    21. }
    22. /// <summary>
    23. /// Sets the object with the given name.
    24. /// </summary>
    25. /// <typeparam name="T">Type of the object</typeparam>
    26. /// <param name="name">The name of the object</param>
    27. /// <param name="obj">The object</param>
    28. public void Set<T>(string name, T obj)
    29. {
    30. if (_getters.ContainsKey(name))
    31. _getters[name] = delegate (SharedBase sb) {
    32. return obj;
    33. };
    34. }
    35. /// <summary>
    36. /// Get the names of all properties with the given type.
    37. /// </summary>
    38. /// <typeparam name="T">The typ the properties should be</typeparam>
    39. /// <returns>List of names</returns>
    40. public List<string> GetProperties<T>()
    41. {
    42. List<string> result = new List<string>();
    43. foreach (var pair in _getters)
    44. {
    45. if (pair.Value.GetType() == typeof(T))
    46. {
    47. result.Add(pair.Key);
    48. }
    49. }
    50. return result;
    51. }
    52. #region implemented abstract members of SharedVariable
    53. public override object GetValue ()
    54. {
    55. return null;
    56. //throw new System.NotImplementedException ();
    57. }
    58. public override void SetValue (object value)
    59. {
    60. //throw new System.NotImplementedException ();
    61. }
    62. #endregion
    63. }

    C#-Quellcode

    1. ​using UnityEngine;
    2. using System.Collections;
    3. using System.Reflection;
    4. namespace BehaviorDesigner.Runtime
    5. {
    6. [System.Serializable]
    7. public class SharedTransform1 : SharedBase
    8. {
    9. public Transform Value { get { return mValue; } set { mValue = value; } }
    10. [SerializeField]
    11. private Transform mValue;
    12. public void Init()
    13. {
    14. PropertyInfo[] infos = typeof(Transform).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    15. foreach (PropertyInfo info in infos)
    16. {
    17. Debug.Log(info.Name);
    18. //_getters.Add(info.Name, sb => info.GetValue(((SharedTransform1) sb).Value, null));
    19. _getters.Add (info.Name, delegate (SharedBase sb) {
    20. SharedTransform1 trans = (SharedTransform1) sb;
    21. return info.GetValue(trans.Value, null);
    22. });
    23. }
    24. Debug.Log(_getters["position"](this));
    25. }
    26. public override object GetValue() { return mValue; }
    27. public override void SetValue(object value) { mValue = (Transform)value; }
    28. public override string ToString() { return (mValue == null ? "null" : mValue.name); }
    29. public static implicit operator SharedTransform1(Transform value) { var sharedVariable = new SharedTransform1(); sharedVariable.SetValue(value); return sharedVariable; }
    30. }
    31. }


    An die, die es nicht gesehen haben, der obige Code ist nur unter Unity lauffähig.

    ErfinderDesRades schrieb:

    ja, was steht denn nu im Log

    LaMiy schrieb:

    Values sind null

    Also es wird None zurückgegeben. Heißt so viel wie null.

    C#-Quellcode

    1. public void Init()
    2. {
    3. PropertyInfo[] infos = typeof(Transform).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    4. foreach (PropertyInfo info in infos)
    5. {
    6. //Debug.Log(info.Name);
    7. Debug.Log("This Get Value " + info.GetValue(this.Value, null));
    8. //_getters.Add(info.Name, sb => info.GetValue(((SharedTransform1) sb).Value, null));
    9. _getters.Add (info.Name, delegate (SharedBase sb) {
    10. SharedTransform1 trans = (SharedTransform1) sb;
    11. Debug.Log("Delegate " + info.GetValue(trans, null));
    12. return info.GetValue(trans.Value, null);
    13. });
    14. }
    15. Debug.Log(_getters["position"](this));
    16. }


    In Zeile 7 wird das Richtige ausgegeben.
    ich ahne, wasses sein könnte. Änder mal auf die GoesTo-Syntax:

    C#-Quellcode

    1. public void Init()
    2. {
    3. PropertyInfo[] infos = typeof(Transform).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    4. foreach (PropertyInfo info in infos)
    5. {
    6. //Debug.Log(info.Name);
    7. Debug.Log("This Get Value " + info.GetValue(this.Value, null));
    8. //_getters.Add(info.Name, sb => info.GetValue(((SharedTransform1) sb).Value, null));
    9. _getters.Add (info.Name, sb => {
    10. SharedTransform1 trans = (SharedTransform1) sb;
    11. Debug.Log("Delegate " + info.GetValue(trans, null));
    12. return info.GetValue(trans.Value, null);
    13. });
    14. }
    15. Debug.Log(_getters["position"](this));
    16. }
    Das müsste eine Warnung oder sogar Fehler geben, weil die Laufvariable in der anonymen Methode addressiert wird.