jeudi 3 mars 2022

Dealing with derived properties with same name as in base class using reflection

I will try to explain my best, but it is a tricky one to explain.

I am having a problem using reflection when a derived object redefines a property already in a base class.

Let's consider the following classes to start with:

// The base class
namespace MyNamesapce
{
    [DataContract]
    public abstract class MyClassBase: IMyClassBase
    {
        [JsonConstructor]
        public MyClassBase()
        {

        }

        public string Info { get; set; }
        public string Unit { get; set; }
        public string Name { get; set; }
    }
}

// MyClassArray is most of the time used
namespace MyNamesapce
{
    [DataContract]
    public class MyClassArray<TType> : MyClassBase, IMyClassArray<TType>
    {
        public MyClassArray()
        {
        }

        [JsonConstructor]
        public MyClassArray(IEnumerable<TType> value, TType minValue, TType maxValue)
        {
            MinValue = minValue;
            MaxValue = maxValue;
            Value = value;
        }

        [DataMember]
        public IEnumerable<TType> Value { get; set;  }

        [DataMember]
        public TType MinValue { get; set; }

        [DataMember]
        public TType MaxValue { get; set; }
    }
}

// In some rare cases we need 2D arrays
namespace MyNamesapce
{
    [DataContract]
    public class MyClass2DArray<TType> : MyClassArray<TType>, IMyClass2DArray<TType>
    {
        private int[] _arraySize { get; set; }

        public MyClass2DArray()
        {
        }

        [JsonConstructor]
        public MyClass2DArray(TType[][] value, TType minValue, TType maxValue)
        {
            MinValue = minValue;
            MaxValue = maxValue;

            _arraySize = new int[2] { value.Count(), value[0].Length };

            Value = value;
        }

        [DataMember]
        public new TType[][] Value
        {
            get
            {
                TType[][] array2D = new TType[_arraySize[0]][];

                // Reconstruct the 2D array
                TType[] tmpArray;
                int startIdx = 0;
                for (int numArrays = 0; numArrays < _arraySize[0]; numArrays++)
                {
                    tmpArray = new TType[_arraySize[1]];
                    Array.Copy(base.Value.ToArray(), startIdx, tmpArray, 0, _arraySize[1]);
                    startIdx += _arraySize[1];

                    array2D[numArrays] = tmpArray;
                }

                return array2D;
            }
            set
            {
                // Should not be able to set _value to null
                if (value == null)
                    return;

                base.Value = value.SelectMany(v => v).ToArray();
            }
        }
    }
}

I now need to get all the properties from all instances of MyClassArray and MyClassArray2D. You will say, there are plenty of threads discussing that very point, just use "GetType().GetProperties()" for the former and use "GetType().GetProperty(..., BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)" for the latter.
The problem is that I do not know in advance which class is being processed. In my system when deserialising a Json, instances of both MyClassArray and MyClassArray2D have to be reconstructed, which is done using the following setter:

public static void SetProperty(this Object obj, string propName, Object value)
{
    PropertyInfo info = null;
    object[] indexer = null;

    string[] nameParts = propName.Split('.');

    if (obj == null) { return; }

    var props = obj.GetType().GetProperties();

    for (int idx = 0; idx < nameParts.Count() - 1; idx++)
    {
        try
        {
            indexer = null;

            // Try to access normal property
            info = obj.GetType().GetProperty(nameParts[idx]);

            if (info == null)
                continue;

            obj = info.GetValue(obj, indexer);
        }
        catch
        {
            info = null;
            indexer = null;
        }
    }

    if (obj != null)
    {

        // !!! Note that here, using declare only will only work when using derived classes
        PropertyInfo propertyToSet = obj.GetType().GetProperty(nameParts.Last(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); // | BindingFlags.DeclaredOnly);
        propertyToSet?.SetValue(obj, value);

    }
    else
    {
        throw new SystemException($"Could not find the property {propName}");
    }

}

As you can see an object is passed in to SetProperty() (that can be of any type).
When it is of type MyClassArray, there are no problems, but if it is of type MyClassArray2D it does not quite work as the latter redefines "Value", which will break the logic as 2 properties called value will exist. I need a way to detect that.
The first loop seems to do the right thing. "obj = info.GetValue(obj, indexer);" will return "obj" containing all the versions of "Value". The problem is in the next part of SetProperty().
How can I detect when more than one "Value" property is in "obj"? And how to always pick the derived version of "Value"?
Also if I just use "BindingFlags.DeclaredOnly" as done here in my code snipet, properties from the base class get lost/disappear, which is undesirable.
Is there maybe a way to return in "obj" all the properties without the duplicates coming from the base class? Or some kind of property filter maybe?





Aucun commentaire:

Enregistrer un commentaire