gdoron
gdoron

Reputation: 150253

Cache reflected properties names

I have a method similar to this one:

static string GetVariableName<T>(Expression<Func<T>> expression)
{
    var body = expression.Body as MemberExpression;

    return body.Member.Name;
}

That give me the variables names. Everyone who mentions Reflection say It's bad for performance, So I want to cache the result so the reflection can occur only one single time for each var. Example:

GetVariableName(() => Model.Field1) // Does Reflection.
GetVariableName(() => Model.Field2) // Does Reflection.
GetVariableName(() => Model.Field1) // Uses Cache.
GetVariableName(() => Model.Field2) // Uses Cache.

I'm using this Util to log parameters And I want start using it to produce JQuery selectors in Asp.net Mvc3 application

$('#'+ @(GetVariableName(()=> Model.FieldName))).Val();

Any ideas?

Upvotes: 1

Views: 1156

Answers (3)

Darin Dimitrov
Darin Dimitrov

Reputation: 1038710

Everyone who mentions Reflection say It's bad for performance

Sure, but in this case you already have the MemberInfo from the lambda expression. The compiler has already built the expression tree. You don't need to fetch it using reflection which is what is slow. What would have been expensive is the following:

static string GetVariableName(string expression)
{
    // use reflection to find the property given the string and once you have the property
    // get its name
    ...
}

That's how all the strongly typed helpers in ASP.NET MVC work. You don't need to cache anything if you use the strongly typed lambda expression version.

Upvotes: 2

Branko Dimitrijevic
Branko Dimitrijevic

Reputation: 52107

You should be able to do something like this...

class Foo {

    public Foo() {
        m_Field1Name = new Lazy<string>(() => GetVariableName(() => Field1));
        m_Field2Name = new Lazy<string>(() => GetVariableName(() => Field2));
    }

    public int Field1 { get; set; }
    public int Field2 { get; set; }

    public string Field1Name {
        get {
            return m_Field1Name.Value;
        }
    }
    readonly Lazy<string> m_Field1Name;

    public string Field2Name {
        get {
            return m_Field2Name.Value;
        }
    }
    readonly Lazy<string> m_Field2Name;

    public static string GetVariableName<T>(Expression<Func<T>> expression) {
        var body = expression.Body as MemberExpression;
        return body.Member.Name;
    }

}

Benchmarking the cached names versus non-cached shows significant difference...

class Program {

    static void Main(string[] args) {

        var foo = new Foo();

        const int count = 1000000;
        var sw = new Stopwatch();

        sw.Restart();
        for (int i = 0; i < count; ++i) {
            string name1 = foo.Field1Name;
            string name2 = foo.Field2Name;
        }
        sw.Stop();
        Console.Write("Cached:\t\t");
        Console.WriteLine(sw.Elapsed);

        sw.Restart();
        for (int i = 0; i < count; ++i) {
            string name1 = Foo.GetVariableName(() => foo.Field1);
            string name2 = Foo.GetVariableName(() => foo.Field2);
        }
        sw.Stop();
        Console.Write("Non-cached:\t");
        Console.WriteLine(sw.Elapsed);

    }

}

This prints:

Cached:     00:00:00.0176370
Non-cached: 00:00:12.9247333

Upvotes: 1

Razor
Razor

Reputation: 17498

Have you considered using attributes? You could reflect over the model once and cache those results instead.

[AttributeUsage(AttributeTargets.Property, AllowMultiple= false)]
class JQueryFieldNameAttribute : Attribute {

    public string Name { get; private set; }

    public JQueryFieldNameAttribute(string name)
    {
        Name = name;
    }
}

class Model {
    [JQueryFieldName("#clientid")]
    public string Foo { get; set; }
}

void Main()
{
    var type = typeof(Model);

    var attributes = type.GetProperties()
                         .SelectMany (t => t.GetCustomAttributes(typeof(JQueryFieldNameAttribute), true));

    var cache = new Dictionary<int, IEnumerable<JQueryFieldNameAttribute>>();

    // Cache results for this type only
    cache.Add(type.GetHashCode(), attributes);

    foreach (JQueryFieldNameAttribute a in attributes)
    {
        Console.WriteLine (a.Name);
    }   
}

Upvotes: 0

Related Questions