Jonathan
Jonathan

Reputation: 7551

Getting property names at compile time

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?

Upvotes: 7

Views: 4505

Answers (3)

Vijay Parmar
Vijay Parmar

Reputation: 809

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;
}

Upvotes: 4

sboisse
sboisse

Reputation: 5498

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.

Upvotes: 9

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174299

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.

Upvotes: 8

Related Questions