1

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?

gdoron
  • 147,333
  • 58
  • 291
  • 367

3 Answers3

2

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.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • hmmm... Thanks. can you please give an example of a "real" reflection? What is exactly the "Reflection horror"? – gdoron Jan 09 '12 at 08:43
  • BTW, what do you think on the way to build the jquery selectors? this way if you refactor one property name you don't need changing all you Jquery code. – gdoron Jan 09 '12 at 08:44
  • @gdoron, the [GetProperty](http://msdn.microsoft.com/en-us/library/kz0a8sxy.aspx) method is a good example of using reflection to obtain a property given some string. Once you have the PropertyInfo and an instance of the object you could also fetch the value. As far as your second question is concerned, I think that it is absolutely horrible to mix javascript and server side code. Personally I keep my javascript in separate files. – Darin Dimitrov Jan 09 '12 at 09:35
  • It doesn't mix Javascript and Serverside code. It's an HTML helper. like @TextBoxfor(m=>m.PropertyName); ==> @SelectorFor(m=>m.PropertyName); This way changing one Property name doesn't breake all yours java script. Well I think it's terrific. – gdoron Jan 09 '12 at 17:07
  • @gdoron, then what's with the `$('#' + ...` bit in the beginning followed by server side expression? – Darin Dimitrov Jan 09 '12 at 17:18
  • I re-designed It. It should be `@SelectorFor(m=>m.PropertyName)` which produces $('#PropertyName'). usage: `@(SelectorFor(m=>m.PropertyName)).val()`. any better? – gdoron Jan 09 '12 at 17:22
  • @gdoron, so it still looks like you are putting your javascript inside your view, right? When the Razor page is rendered you get markup + script mixed in the same file which is what I was against. For me markup goes into views and scripts go into separate javascript files. – Darin Dimitrov Jan 09 '12 at 17:27
  • As you well know, there is a problem separating JS from the views in MVC because of the routing. see my question and two answers [here](http://stackoverflow.com/questions/7902213/asp-net-mvc-razor-symbol-in-js-file). Anyway if you separate Your JS into separate .js file, each naming refactor might cause breaks in your Java script. – gdoron Jan 09 '12 at 18:12
  • There are so many ways to circumvent this. One is to use HTML5 data-* attributes, but of course depending on the specific case and the problem you are trying to solve there could be better ways. I will post an answer to your linked question with an example. As far as naming refactoring is concerned, well, personally I never make my javascript depend on server side names. I prefer to [write jQuery plugins](http://docs.jquery.com/Plugins/Authoring) which I attach to DOM elements with their respective selectors. But once again to stay on topic for this question: you don't need to cache anything. – Darin Dimitrov Jan 09 '12 at 19:35
1

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
Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
  • Thanks for your input. But now the class has to "know" all the properties it will get. – gdoron Jan 09 '12 at 04:07
  • @gdoron In your example, when you write `() => Model.Field1`, you already "know" the property at compile-time. Whether you'll cache names in the same class as properties is a different matter. If you can't change the existing class, you can always create a new one, just for caching purposes. You don't even need an instance of the original class, you can write `GetVariableName(() => default(Foo).Field1)` - the lambda will never actually be called, so it doesn't matter that the `default(Foo)` is null. – Branko Dimitrijevic Jan 09 '12 at 05:01
0

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);
    }   
}
Razor
  • 17,271
  • 25
  • 91
  • 138