Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Retrieving order of declaration of class members

Discussion in 'Scripting' started by TurgutHakki, May 18, 2022.

  1. TurgutHakki

    TurgutHakki

    Joined:
    Jun 15, 2017
    Posts:
    33
    Hello
    Is there any way to sort MemberInfo's returned by Type.GetMembers() by their order of declaration as unity does in inspector?
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Not directly, no. The documentation for Type.GetMembers is very explicit, the order is not guaranteed.

    You can of course solve this by parsing the .cs file itself, and finding the members there. I believe that's what Unity is doing internally. That can end up being annoying, but you only need to find the fields that have names that match the members you're looking for, so it's probably not that bad.
     
  3. TurgutHakki

    TurgutHakki

    Joined:
    Jun 15, 2017
    Posts:
    33
    Instead of pure C# I'm investigating employing Mono.Cecil and/or Roslyn.
     
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    While it is true that the order is not guaranteed, AFAIK currently the reflection methods return the members based on the metadata token and those are defacto in the declaration order.

    Of course there are ambiguities when you use partial classes as they actually represent one class, so things have to be merged in some way. Here is makes not much sense to talk about declaration order. If you have 5 files with a partial class declaring various things each, what should be the expected order?

    I'm not really sure Unity does manual code analysis. I actually doubt that. I'm pretty sure they also just rely on the metadata order. Otherwise, how would they handle pre-compiled assemblies which only contain metadata tokens and nothing else. With assemblies there's no source code available.

    The documentation simply does not specify the order and therefore recommends to not rely on it. Though for most practical purposes it should be consistent. Note that it may be a totally different thing when we talk about runtime and IL2CPP as things get cross compiled to C++ code. So some platforms may work different.

    Remember when GetComponents said the order was not guaranteed? It was consistent for all practical purposes but they didn't want to restrict themself to this definition. In the end the components just sit in a list on the native side in exactly that order. Later, I think with the audio filter implementation, they now guarantee the order to be the same as shown in the inspector since the "stacking" of components was now used. There hasn't really changed anything (besides we got an API to move components up and down) but it's just now guaranteed.

    So yes, from a specification point of view it's not guaranteed and could potentially change in the future or with different platform implementations. Though for practical purposes it should be fairly consistent. Of course it depends what you need this information for. There are serializers out there that also rely on the "natural" order which of course would / will break when the order changes. Though most rely on the actual member names.
     
  5. TurgutHakki

    TurgutHakki

    Joined:
    Jun 15, 2017
    Posts:
    33
    Thank you. It really appears that Unity is sorting by metadata tokens. However metadata tokens are grouped by member's type (property, method field etc.) Only way to do kind of sort I needed turned out to be too much effort. I was trying to write a "default inspector" that can draw buttons / properties in declaration order.
     
  6. TurgutHakki

    TurgutHakki

    Joined:
    Jun 15, 2017
    Posts:
    33
    If anyone is interested, I ended up doing this :
    Code (CSharp):
    1.  
    2.  
    3. // ShowInspectorAttribute.cs
    4. public class ShowInInspectorAttribute : Attribute
    5. {
    6. }
    7.  
    8. // ButtonAttribute.cs
    9. public class ButtonAttribute : Attribute
    10. {
    11. }
    12.  
    13.  
    14. // ObjectEditor.cs
    15. [CanEditMultipleObjects]
    16. [CustomEditor(typeof(UnityEngine.Object), true)]
    17. public class ObjectEditor : Editor
    18. {
    19.   protected delegate void AttributeDrawer(UnityEngine.Object o, MemberInfo m);
    20.  
    21.   protected void drawProperty(UnityEngine.Object o, MemberInfo mi)
    22.   {
    23.     if (mi.isNull())
    24.       return;
    25.  
    26.     bool orgEnabled = GUI.enabled;
    27.     GUI.enabled = false;
    28.     EditorGUILayout.TextField(ObjectNames.NicifyVariableName(mi.Name), (mi as PropertyInfo).GetValue(o)?.ToString());
    29.     GUI.enabled = orgEnabled;
    30.   }
    31.  
    32.   protected void drawMethod(UnityEngine.Object o, MemberInfo member)
    33.   {
    34.     MethodInfo mi = member as MethodInfo;
    35.     if (mi.isNull() || mi.GetParameters().Length > 0)
    36.       return;
    37.  
    38.     if (GUILayout.Button(ObjectNames.NicifyVariableName(mi.Name))) {
    39.       mi.Invoke(mi.IsStatic ? null : o, new object[] {});
    40.     }
    41.   }
    42.  
    43.   protected void drawAttributes(UnityEngine.Object o, string label, AttributeDrawer drawer, IEnumerable<MemberInfo> members)
    44.   {
    45.     bool orgEnabled = GUI.enabled;
    46.     if (members.Count() > 0) {
    47.       GUI.enabled = true;
    48.       GUILayout.Space(10);
    49.       GUILayout.Label(label);
    50.       GUI.enabled = false;
    51.       foreach(var member in members) {
    52.         drawer(o, member);
    53.       }
    54.     }
    55.   }
    56.  
    57.   public override void OnInspectorGUI()
    58.   {
    59.     DrawDefaultInspector();
    60.  
    61.     Type type = serializedObject.targetObject.GetType();
    62.     UnityEngine.Object o = serializedObject.targetObject;
    63.  
    64.     drawAttributes(o, "Static properties", drawProperty, type.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(mi => Attribute.IsDefined(mi, typeof(ShowInInspectorAttribute))).OrderBy(m => m.MetadataToken));
    65.     drawAttributes(o, "Instance properties", drawProperty, type.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(mi => Attribute.IsDefined(mi, typeof(ShowInInspectorAttribute))).OrderBy(m => m.MetadataToken));
    66.     drawAttributes(o, "Static methods", drawMethod, type.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(mi => Attribute.IsDefined(mi, typeof(ButtonAttribute))).OrderBy(m => m.MetadataToken));
    67.     drawAttributes(o, "Instance methods", drawMethod, type.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(mi => Attribute.IsDefined(mi, typeof(ButtonAttribute))).OrderBy(m => m.MetadataToken));
    68.  
    69.     serializedObject.ApplyModifiedProperties();
    70.   }
    71.  
    72. }
    73.  
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    Since you implemented an actual "Editor", note that Editors do receive an OnEnable message. So you may want to cache some of your on-the-fly generation. Using reflection, especially when used often, can be really slow. It may work decently, however keep in mind if your inspector is open it will affect Unity as a whole. Editor tools should be as lightweight as possible.

    Currently you call GetMembers 4 times. This method will return a new array with all members that the type has (all fields, methods, properties). You then filter out those which has a certain attribute. Reflection provides specific methods to directly get properties or methods only. Also since you filter the list afterwards, you could just get the member list once and just filter on "IsStatic" to distinguish the two categories.

    I would recommend you simply declare 4 seperate arrays or Lists for your 4 categories. Fill them inside OnEnable and just use those in OnInspectorGUI. This should eliminate most of the garbage generated as well as increase the overall performance quite a bit. Also I would reevalulate your GUI.enabled "foo" :). It seems all over the place and some things don't make much sense / are not complete.
     
  8. TurgutHakki

    TurgutHakki

    Joined:
    Jun 15, 2017
    Posts:
    33
    It's simplified. And I really hate to optimize editor code. I'm ok with slow :)