1
\$\begingroup\$

I have close to a hundred classes where I need to get arbitrary properties from them at runtime. The calling class knows which properties it wants at run time, but not at compile time. Looking for any suggestions on a quick way to do this.

My ideas so far:
Option 1: Add "public object Get(string prop) {...}" to all classes, then invoke myClass.Get(prop)
Option 2: Use reflection: typeof(myClass).GetProperty(prop).GetValue(myClass)
Option 3: Use ComponentModel: TypeDescriptor.GetProperties(typeof(myClass))[prop].GetValue(myClass);
Option 4: HyperTypeDescriptionProvider

Trying these out gave me runtimes of:
301ms (baseline accessing the property directly)
1308ms Option 1
4383ms Option 2
5229ms Option 3
Option 4 threw System.TypeInitializationException constantly and never completed.

Full Executable sample:

//using Hyper.ComponentModel;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;

namespace Sandbox2
{
    public class MyTable
    {
        public string col0 {get;set;}
        public int col1 { get; set; }
        public string[] col2 { get; set; }
        public object col3 { get; set; }
        public string col4 { get; set; }
        public object Get(string prop)
        {
            switch(prop)
            {
                case "col0":
                    return col0;
                case "col1":
                    return col1;
                case "col2":
                    return col2;
                case "col3":
                    return col3;
                case "col4":
                    return col4;
            }
            throw new InvalidOperationException("Non-existent prop");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            const int CYCLES = 5000000;
            //const int CYCLES = 1;
            GC.Collect();
            Stopwatch timer = new Stopwatch();
            string[] goalProps = { "col2", "col0", "col4" };
            MyTable myTable = new MyTable();
            timer.Start();
            for (int i = 0; i < CYCLES; ++i)
            {
                object[] res = { myTable.col2, myTable.col0, myTable.col4 };
            }
            timer.Stop();
            System.Diagnostics.Debug.WriteLine("Elapsed: " + timer.ElapsedMilliseconds);
            GC.Collect();
            timer.Start();
            for (int i=0; i<CYCLES; ++i)
            {
                object[] res = { myTable.Get(goalProps[0]), myTable.Get(goalProps[1]), myTable.Get(goalProps[2]) };
            }
            timer.Stop();
            System.Diagnostics.Debug.WriteLine("Elapsed: " + timer.ElapsedMilliseconds);
            timer.Reset();
            GC.Collect();
            timer.Start();
            for (int i = 0; i < CYCLES; ++i)
            {
                Type t = myTable.GetType();
                PropertyInfo[] goalPropsInfo = { t.GetProperty(goalProps[0]), t.GetProperty(goalProps[1]), t.GetProperty(goalProps[2]) };
                object[] res = { goalPropsInfo[0].GetValue(myTable), goalPropsInfo[1].GetValue(myTable), goalPropsInfo[2].GetValue(myTable) };
            }
            timer.Stop();
            System.Diagnostics.Debug.WriteLine("Elapsed: " + timer.ElapsedMilliseconds);
            timer.Reset();
            GC.Collect();
            timer.Start();
            for (int i = 0; i < CYCLES; ++i)
            {
                Type t = myTable.GetType();
                //HyperTypeDescriptionProvider.Add(t);
                PropertyDescriptorCollection props = TypeDescriptor.GetProperties(t);
                PropertyDescriptor[] goalPropsInfo = { props[goalProps[0]], props[goalProps[1]], props[goalProps[2]] };
                object[] res = { goalPropsInfo[0].GetValue(myTable), goalPropsInfo[1].GetValue(myTable), goalPropsInfo[2].GetValue(myTable) };
            }
            timer.Stop();
            System.Diagnostics.Debug.WriteLine("Elapsed: " + timer.ElapsedMilliseconds);
        }
    }
}
````
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Use BenchmarkDotNet to properly measure performance. I also recommend the book Pro .NET Benchmarking. | Perhaps you should use a Dictionary<string, T> to set and get values by name in runtime? \$\endgroup\$ Apr 15, 2022 at 4:04
  • \$\begingroup\$ You should cache PropertyInfo. stackoverflow.com/q/7999652/5045688 \$\endgroup\$ Apr 18, 2022 at 13:57
  • \$\begingroup\$ Does the class that wants the property at runtime know it's type? \$\endgroup\$ Apr 18, 2022 at 14:00
  • \$\begingroup\$ @AlexanderPetrov Tried that, but maybe I did it wrong, that link is what led me to HyperTypeDescriptor which didn't work "as-is". This link looks promising: stackoverflow.com/questions/17660097/…. \$\endgroup\$
    – DKATyler
    Apr 18, 2022 at 17:17
  • \$\begingroup\$ @CharlesNRice Sort of, the calling class has plenty of function overloading to handle any/all property types that MyTable could contain. E.g. func(string ...) func(double ...) etc. \$\endgroup\$
    – DKATyler
    Apr 18, 2022 at 17:20

3 Answers 3

2
\$\begingroup\$

A way to optimize is to use a static constructor to automatically prefill a static lookup table (dictionary) to map property names to the property getters.

This being static is done once for the class and reused across all instances so it has a low overhead since the cost is amortized across all instance usages. Also can be easily done for all properties as shown below or only for selected ones as required.

After that extracting the property value is trivial.

public class MyTable
{
    // map of property names to functions which retrieve the property
    static Dictionary<string, Func<object, object>> _props = 
        new Dictionary<string, Func<object, object>>();
    
    static MyTable()
    {
        foreach(var property in typeof(MyTable).GetProperties())
            _props[property.Name] = property.GetValue;
             // Note: property.GetValue may be easier to understand as 
             // (obj => property.GetValue(obj)) where object is an instance of the class
    }

    public string col0 { get; set; }
    public int col1 { get; set; }
    public string[] col2 { get; set; }
    public object col3 { get; set; }
    public string col4 { get; set; }
    
    public object Get(string prop)
    {
        Func<object, object> getProp;
        
        if (_props.TryGetValue(prop, out getProp))
            return getProp(this);
        
        throw new InvalidOperationException("Non-existent prop");
    }
}

Usage example

public static void Main()
{
    var myTable = new MyTable();

    myTable.col0 = "0";

    var col0 = myTable.Get("col0");
    
    Console.WriteLine(col0);
}
\$\endgroup\$
1
  • \$\begingroup\$ I hadn't realized/known that you could save Functions to a dictionary, and execute them too! \$\endgroup\$
    – DKATyler
    Apr 20, 2022 at 5:48
1
\$\begingroup\$

It seems to me that there is a modeling problem here.

I would suggest using a Dictionary<string, string>

Also add error handling in case a key is not presented in the dictionary.

\$\endgroup\$
1
  • \$\begingroup\$ The class params are not necessarily all string, I typed that up as a quick example (will edit question so there's a variety to avoid confusion.) Also, while some access is via "Get" most access will be by the much better, and safer myTable.col0. This would imply trying to keep the dictionary up to date with any change to the underlying class. \$\endgroup\$
    – DKATyler
    Apr 18, 2022 at 11:11
1
\$\begingroup\$

Depends how much speed you want to tweak. One option is make the backing fields of the properties a table. Like so

public class MyTable
{
    private readonly Dictionary<string, object> _backingTable = new Dictionary<string, object>();

    public string col0 { get => Get<string>(); set => Set(value); }
    public int col1 { get => Get<int>(); set => Set(value); }
    public string[] col2 { get => Get<string[]>(); set => Set(value); }
    public object col3 { get => Get<object>(); set => Set(value); }
    public string col4 { get => Get<string>(); set => Set(value); }

    public object this[string prop] 
    { 
        get 
        {
            if (_backingTable.TryGetValue(prop, out object value))
            {
                return value;
            }
            return null;
        }
    }

    private void Set<TValue>(TValue value, [CallerMemberName] string property = "")
    {
        _backingTable[property] = value;
    }

    private TValue Get<TValue>([CallerMemberName] string property = "")
    {
        if (_backingTable.TryGetValue(property, out var value))
        {
            return (TValue)value;
        }
        return default(TValue);
    }
}

On my machine this code does slow down the direct access a bit because it has to do through the table but it's not terrible. The table access is faster than the switch statement, on my machine, and has benefit of adding more properties don't have to change the switch statement. Downside it's more code to implement, just tedious, than standard get/set. If don't want to make backing field table then switch statement will be faster even if caching the propertyinfo and delegates. But switch statement has downside of every time adding a property have to remember to update the switch statement, not very DRY. Could probably make a Source Generator for the switch statement to make maintenance easier.

\$\endgroup\$
1
  • \$\begingroup\$ Oh ho! I didn't consider re-defining the props as get from the dictionary. \$\endgroup\$
    – DKATyler
    Apr 19, 2022 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.