Search code examples
c#reflectionlambdaexpression-trees

Get static field from nested class via expression tree


I have these classes:

public class Entity
{
    public static readonly EntitySchema Schema = new EntitySchema();
}

public abstract class BaseSchema
{
    public abstract string Name {get;}
}

public class EntitySchema : BaseSchema
{
    public override string Name => "Schema";
}

Now, I want to access EntitySchema.Name from a method, which does not know anything about Entity (cannot access the static field).

I could do this with reflection:

static BaseSchema GetSchema<T>()
{
    var pr = typeof(T).GetField("Schema");
    var schema = pr.GetValue(null);
    return schema as BaseSchema;
}

but compared to a direct call Entity.Schema.Name the reflection version is 50x slower.

Is there a way to convert the reflection version into an Expression Tree and pre-compile the call?


Solution

  • Sure you can do that, but .NET requires to have the full field definition (EntitySchema global::Entity.Schema in this case) to get the actual value. Reflection gets you this definition. You'd need to make a delegate for every type, ie:

    public static class SchemaGetter
    {
        private static readonly Dictionary<object, Func<BaseSchema>> _lookup = new Dictionary<object, Func<BaseSchema>>();
    
        public static BaseSchema Get<T>()
        {
            Func<BaseSchema> action;
    
            if(!_lookup.TryGetValue(typeof(T), out action))
            {
                action = MakeDelegate<T>();
    
                _lookup.Add(typeof(T), action);
            }
    
            return action();            
        }
    
        private static Func<BaseSchema> MakeDelegate<T>()
        {
            // We did this before already...
    
            FieldInfo field = typeof(T).GetField("Schema", BindingFlags.Public | BindingFlags.Static);
            var fieldExpression = Expression.Field(null, field);
    
            var lambda = Expression.Lambda<Func<BaseSchema>>(fieldExpression);
    
            return lambda.Compile();
        }
    }
    

    Followed by BaseSchema schema = SchemaGetter.Get<Entity>() to get the actual schema.

    Perhaps its sufficient enough to cache the result that you've got from your initial GetSchema<T>() implementation (wrap it in a dictionary lookup). I think it must in this case, since the field is static anyway.