0

I have written a data parser for my script engine that uses reflection to get the value of actual program data classes. I have a hierarchy of classes where some of my sub-classes hide other sub-classes.

C#'s runtime binder logic knows how to handle this and won't recognize members of the hidden class, but my logic can't tell the difference between a hidden member and a valid member.

The code below is a super-truncated version that illustrates my issue. As you can see from the output, both the timestamp member of the original PSR class, and the timeStamp (different case) member of the updated PSR class are found.

I'm looking for an attribute or method that can help me decide if the member I'm looking at is hidden or accessible.


using System;
using System.Reflection;`

namespace test
{
    static class PRD
    {
        public static dynamic prdData { get; private set; } = new PRD_P20();
    }

    public abstract class PRD_DATA
    {
        public BLE ble = new BLE();
        public class BLE : PRD_BLE_BASE { }
    }

    public abstract class PRD_BLE_BASE
    {
        public PSR psr = new PSR();
    }
    public class PSR
    {
        public string timestamp = "original";
    }

    public class PRD_P20 : PRD_DATA
    {
        /* Instantiate new version BLE and hide the parent class  */
        public new BLE ble = new BLE();

        public new class BLE
        {
            public PSR psr = new PSR();
        }

        public class PSR
        {
            public string timeStamp = "updated";
        }

    }
    class Program
    {
        static void Main(string[] args)
        {
            var o = PRD.prdData;
            foreach (FieldInfo f in o.GetType().GetFields())
            {
                Console.WriteLine(f.Name + " " + f.DeclaringType);

                object o1 = f.GetValue(o);
                foreach (FieldInfo f1 in o1.GetType().GetFields())
                {
                    Console.WriteLine("----" + f1.Name);

                    object o2 = f1.GetValue(o1);
                    foreach (FieldInfo f2 in o2.GetType().GetFields())
                    {
                        Console.WriteLine("--------" + f2.Name + ": " + f2.GetValue(o2));
                    }
                }
            }

            Console.WriteLine(PRD.prdData.ble.psr.timestamp);   // Fails RuntimeBinderException
            Console.WriteLine(PRD.prdData.ble.psr.timeStamp);

            Console.ReadKey();
        }
    }}

Output:

ble test.PRD_P20
----psr
--------timeStamp: updated
ble test.PRD_DATA
----psr
--------timestamp: original
Digilee
  • 35
  • 6
  • Maybe I'm just tired but I have read your question twice now and I have no idea what it is you're trying to accomplish. Can you explain more what you mean by "hidden"? Can you provide both expected and actual output? – John Wu May 02 '23 at 07:44
  • I thought I was being clear. PRD_P20 is based on PRD_DATA. Both have a sub-class named PSR. The PSR class of PRD_DATA is hidden by the PSR class of PRD_P20, so that the statement PRD.prdData.ble.psr.timestamp is not found. The C# runtime binder knows this, but using reflection, both show up. I need to be able to tell if the object I am accessing with reflection is hidden from normal access by the program. – Digilee May 02 '23 at 13:57
  • I waited too long to edit... Above, I should have said the BLE class of PRD_DATA is hidden by the BLE class of PRD_P20. Basically the same effect. For the output, I just want to see the first 3 lines. – Digilee May 02 '23 at 14:05
  • I think I was confused because your question is not actually about parsing, your PSR class isn't actually a subclass, and your variables are named in a way where you already have to understand the problem to know what they are. I had to do a bit of a rewrite and worked through it just to understand. – John Wu May 02 '23 at 16:40

1 Answers1

0

Sounds like you want to include fields from only the type itself, not its ancestors, so you need to check its declaring type. See the Where clause below.

static void Main(string[] args)
{
    dynamic prdData = PRD.prdData;
    TypeInfo prdDataType = prdData.GetType();
    var filteredFields = prdDataType.GetFields().Where( x => x.DeclaringType == prdDataType).ToList();
    foreach (FieldInfo prdDataField in filteredFields)
    {
        object prdFieldValue = prdDataField.GetValue(prdData);
        Console.WriteLine("Found field '{0}' declared in type '{1}' with a value of '{2}' which is of type '{3}'", prdDataField.Name, prdDataField.DeclaringType, prdFieldValue, prdFieldValue.GetType().FullName);

        foreach (FieldInfo f1 in prdFieldValue.GetType().GetFields())
        {
            object o2 = f1.GetValue(prdFieldValue);
            foreach (FieldInfo f2 in o2.GetType().GetFields())
            {
                Console.WriteLine("f2.Name:" + f2.Name + ": " + f2.GetValue(o2));
            }
        }
    }

See Fiddle example

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Thanks so much for your patience. I guess I still did not present the question correctly. I had already tried DeclaringType, but that is not the entire answer. What I really need is to find out if the field returned by GetFields() is actually the instantiated version. It could be that the field was NOT derived from that object, but instantiated by it. – Digilee May 02 '23 at 19:09
  • For example, PRD.prdData.ble.psr.timestamp is a logical path for the timestamp variable, but not actually instantiated (at least I don't think so). PRD.prdData.ble.psr.timeStamp follows the same path, except it uses a class that was NOT hidden The runtime binder can do this somehow. I am looking for some attribute or flag that tells me this is the instantiated version of the field – Digilee May 02 '23 at 19:10
  • I see. You might be interested in [this question](https://stackoverflow.com/questions/288357/how-does-reflection-tell-me-when-a-property-is-hiding-an-inherited-member-with-t) – John Wu May 02 '23 at 19:59
  • I saw that one also, I just wasn't sure it applied to my problem. I have solved this issue by noticing that the list of fields returned by GetFields() always seems to be in order of derived class first, followed by one or more ancestor classes. Don't know if this is ALWAYS true, but it seems reasonable and I was able to capitalize on that fact to exit the recursion if the base class of the list member was not the same as the original base class. Thank you for your help. I'm accepting your answer because it was related to how I actually solved it. – Digilee May 02 '23 at 21:50