Search code examples
c#performancestaticsystem.reflectionreflection.emit

C# - Faster way to get set public static fields instead of using Reflection.SetValue / GetValue


I have a scenario where I need to change public static fields during runtime. I understand that I can do it through reflection as below to get set the public static field I want, but it is really slow.

string typeName = "ABC";
string fieldName = "IsA";

Type.GetType(typeName ).GetField(fieldName ).SetValue(null, value);

var value = Type.GetType(typeName ).GetField(fieldName ).GetValue(null);

I would like to know is there any faster way to access such as using Reflection.Emit, Linq.Expression or other methods. As what I know currently most of them only support fields with an instance.


Solution

  • You can use expressions for this. You basically have three options:

    1. Reflection. Slow.
    2. Dynamic compiled expression. Fast.
    3. Typed compiled expression. Super fast.

    In your case it's a bit tricky to go for typed expressions. I guess we cannot assume that all static properties will be of type string? The second option allows you to easily create a fast setter for any field type.

    Note that when compiling expressions, you must maintain a cache for the compiled delegates. The compilation step is very expensive!

    UPDATE: Using typed compiled expressions

    class Program
    {
        public class Foo
        {
            public static string Name;
        }
    
        public static void Main()
        {
            var delegateCache = new Dictionary<(string, string), Action<string>>();
    
            var typeName = typeof(Foo).FullName;
            var fieldName = "Name";
    
            var key = (typeName, fieldName);
    
            // Caching is crucial!
            if (!delegateCache.TryGetValue(key, out var d))
            {
                d = CreateStaticSetter(typeName, fieldName);
                delegateCache.Add(key, d);
            }
    
            d.Invoke("new value");
    
            Console.WriteLine(Foo.Name);
        }
    
        private static Action<string> CreateStaticSetter(string typeName, string fieldName)
        {
            var type = Type.GetType(typeName) ?? throw new ArgumentException();
            var field = type.GetField(fieldName) ?? throw new ArgumentException();
    
            var valueExp = Expression.Parameter(field.FieldType, "value");
            var fieldExp = Expression.Field(null, field);
            var assignExp = Expression.Assign(fieldExp, valueExp);
    
            var expr = Expression.Lambda<Action<string>>(assignExp, valueExp);
            return expr.Compile();
        }
    }
    

    Using dynamic compiled expressions

    class Program
    {
        public class Foo
        {
            public static string Name;
        }
    
        public static void Main()
        {
            var delegateCache = new Dictionary<(string, string), Delegate>();
            
            var typeName = typeof(Foo).FullName;
            var fieldName = "Name";
    
            var key = (typeName, fieldName);
    
            // Caching is crucial!
            if (!delegateCache.TryGetValue(key, out var d))
            {
                d = CreateStaticSetter(typeName, fieldName);
                delegateCache.Add(key, d);
            }
    
            // For a strongly typed delegate, we would use Invoke() instead.
            d.DynamicInvoke("new value");
    
            Console.WriteLine(Foo.Name);
        }
    
        private static Delegate CreateStaticSetter(string typeName, string fieldName)
        {
            var type = Type.GetType(typeName) ?? throw new ArgumentException();
            var field = type.GetField(fieldName) ?? throw new ArgumentException();
            
            var valueExp = Expression.Parameter(field.FieldType, "value");
            var fieldExp = Expression.Field(null, field);
            var assignExp = Expression.Assign(fieldExp, valueExp);
    
            // TODO: Can be further optimized with a strongly typed delegate.
            var expr = Expression.Lambda(assignExp, valueExp);
            return expr.Compile();
        }
    }