Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting property names at compile time

Tags:

c#

I have a class with some properties:

class Foo
{
    public int Bar { get; set; }
    public string Baz { get; set; }
    public bool Quux { get; set; }
    (...)
}

For use in some storage API, I need to specify a subset of these properties, by name as strings:

var props = new string[]
{
    "Bar",
    // Don't want this one... "Baz",
    "Quux",
     ...
};

This works, but is unsafe - if I mistype "Quux", I won't get a compilation error, just (hopefully) a runetime error. I tried reflection - typeof(Foo).GetProperties("Bar") - but that would also fail only in runtime.

Ideally, I'd like to do something like:

var props = new string[]
{
    Magic_GetName(Foo.Bar),
    // Don't want this one... Foo.Baz,
    Magic_GetName(Foo.Quux),
     ...
};

How can I achieve that?

like image 731
Jonathan Avatar asked Apr 10 '13 07:04

Jonathan


3 Answers

In C# 6.0, you can use the nameof() keyword:

And then you write:

var props = new string[]
{
    nameof(Foo.Bar),
    nameof(Foo.Quux),
     ...
};

Everything is done at compile time using this keyword, so it's much better than using lambda expression with code that digs the name of your symbol at runtime. It's better on a performance point of view, and it also works with switch() statements:

switch(e.PropertyName)
{
    case nameof(Foo.Bar):
        break;
}

Using lambda expression or magic get functions, you can't use the switch() statement because the switch() statement requires to use string literals. Since nameof() keywords are converted to string literals at compile time, it works.

like image 173
sboisse Avatar answered Nov 03 '22 10:11

sboisse


You can use expressions for this. The usage would look like this:

Magic_GetName<Foo>(x => x.Bar)

The implementation of Magic_GetName would look like this:

public static string Magic_GetName<TClass>(
    Expression<Func<TClass, object>> propertyExpression)
{
    propertyExpression.Dump();
    var body = propertyExpression.Body as UnaryExpression;
    if (body == null)
    {
        throw new ArgumentException(
            string.Format(
                CultureInfo.InvariantCulture, 
                "The body of the 'propertyExpression' should be an " +
                "unary expression, but it is a {0}", 
                propertyExpression.Body.GetType()));
    }

    var memberExpression = body.Operand as MemberExpression;
    if (memberExpression == null)
    {
        throw new ArgumentException(
            string.Format(
                CultureInfo.InvariantCulture, 
                "The operand of the body of 'propertyExpression' should " +
                "be a member expression, but it is a {0}", 
                propertyExpression.Body.GetType()));
    }
    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
    {
        throw new ArgumentException(
            string.Format(
                CultureInfo.InvariantCulture, 
                "The member used in the expression should be a property, " +
                "but it is a {0}", 
                memberExpression.Member.GetType()));
    }

    return propertyInfo.Name;
}

Update: The title of this question is "Getting property names at compile time".
My answer actually doesn't do that. The method Magic_GetName is executed at runtime and as such has a performance impact.

The .NET 4.5 way using the CallerMemberName attribute on the other hand is really a compile time feature and as such doesn't have a runtime impact. However, as I already said in the comments, it is not applicable in the given scenario.

like image 8
Daniel Hilgarth Avatar answered Nov 03 '22 08:11

Daniel Hilgarth


Much better way of doing that is

GetPropertyName<MemoryDevice>(x => x.DeviceLocator)

public static string GetPropertyName<TClass>(
        Expression<Func<TClass,object>> propertyExpression)
    {
        var body = propertyExpression.ToString();
        body = body.Substring(body.IndexOf(".")+1);
        return body;
    }

another way to do that at runtime is

public static string GetName<TClass>(
    Expression<Func<TClass, object>> propertyExpression)
{
    var body = propertyExpression.Body as UnaryExpression;
    var memberExpression = body.Operand as MemberExpression;
    var propertyInfo = memberExpression.Member as PropertyInfo;

    return propertyInfo.Name;
}
like image 4
Vijay Parmar Avatar answered Nov 03 '22 08:11

Vijay Parmar