5

I would like to be able to retrieve the name of a property of a type using a strongly typed syntax. I already got a function to get a property name of an instance:

public static string PropertyName<T, TReturn>(this T obj, Expression<Func<T, TReturn>> property) where T : class 
{
    MemberExpression body = (MemberExpression) property.Body;
    if (body == null) throw new ArgumentException("The provided expression did not point to a property.");       
    return body.Member.Name;
}

Which can be called like this:

Car car = new Car();
car.PropertyName(x => x.Wheels) //returns "Wheels"

I'm trying to create another function that could support the following:

Type t = Typeof(Car);
t.PropertyName(x => x.Wheels) //should return "Wheels"

Or just (even better!):

Car.PropertyName(x => x.Wheels)

How would I go about this?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
JensOlsen112
  • 1,279
  • 3
  • 20
  • 26
  • Generics are for compile-time known types. You can't have a similar method for `System.Type`. Imagine a case: `Type t = Type.GetType("Hi there"); t.PropertyName(x => ???);` – abatishchev Feb 17 '15 at 19:26
  • 2
    By the way: in Roslyn or C# 6, you just use `nameof()`. – dymanoid Feb 17 '15 at 19:27
  • My question would rather be, why would you need such a construction? What is the use to let such a function, return a string value? – Icepickle Feb 17 '15 at 19:30
  • Sounds like http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – abatishchev Feb 17 '15 at 19:33

3 Answers3

13

You can rewrite your method to use it without creating an instance:

var prop = ReflectionHelper.PropertyName<Car>(x => x.Wheels);

because your don't use obj inside because you don't need it:

public static class ReflectionHelper
{
    public static string PropertyName<T>(Expression<Func<T, object>> property) where T : class 
    {
        MemberExpression body = (MemberExpression)property.Body;
        return body.Member.Name;
    }
}

Note that the return type doesn't have to be strongly-typed, it can be just object.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
  • 2
    Your sample does not compile, since you forget the second type `TReturn` in the call of `PropertyName()`... And writing `ReflectionHelper.PropertyName(x => x.Wheels);` is really boring since you don't want explicitly to write the property type each time you just want to extract it's name ... – rducom Feb 17 '15 at 19:39
  • @abatishchev Thanks this works great! Just a question, would it be possible to convert it to an extension method, like one of the 2 examples in my question? – JensOlsen112 Feb 17 '15 at 20:13
  • 1
    @Jens: hm, not really. Extension method requires an instance to run it on but here you don't have any. – abatishchev Feb 17 '15 at 20:18
  • This is a great answer. I added on to it below, to address the invalid cast exception that @pedro-faustino raised (no pun intended). – Paul Smith Jul 16 '20 at 23:47
2

In C# 6 and higher, the syntactical goodness of using static can make usages of this sort of method much more readable. It will also give you early binding / compile-time checks, so CodeLens will show you usages to your property that used to be referenced as string literals (like in lots of data binding code floating around out there). This makes refactoring much easier when you have to rename properties down the road.

The code here builds on @abatishchev's answer, because in this scenario (where you don't have an instance of the type in question), using extension methods makes the code more verbose, not less.

There’s also an additional line in the PropertyName method to handle the point by @pedro-faustino about exceptions casting UnaryExpression. (That wouldn’t be needed if the method had a second type parameter in the propertyExpression argument, for the return type, e.g. Expression<Func<T, TMember>>).

That said, here’s an example of how you could wire up data bindings in an early-bound manner with this kind of strategy:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using BusinessObjects.Vehicles;
using static MyCoolHelperMethods;

namespace Helpers
{
    public static class MyCoolHelperMethods 
    {
        public static string PropertyName<T>(Expression<Func<T, object>> propertyExpression) where T : class
        {
        var memberExpression = propertyExpression.Body as MemberExpression 
                ?? (propertyExpression.Body as UnaryExpression)?.Operand as MemberExpression;
            return memberExpression?.Member.Name;
        }
    }
}

namespace Application
{
    using System.Windows.Forms;

    public class Car
    {
        public string Make {get; set;}
        
        public string Model {get; set;}
    }

    // imagine this is a form with textboxes for car Make and Model:
    public partial class MyWindowsForm
    {
        public MyWindowsForm()
        {
            var car = new Car();
            
            this.txtCarMake.DataBindings.Add(new Binding(
                propertyName: PropertyName<TextBox>(x => x.Text),
                dataSource: car, 
                dataMember: PropertyName<Car>(x => x.Make),
                // formattingEnabled is needed to avoid invalid cast
                // exceptions assigning the object property to the control:
                formattingEnabled: true));
            
            this.txtCarModel.DataBindings.Add(new Binding(
                propertyName: PropertyName<TextBox>(x => x.Text),
                dataSource: car, 
                dataMember: PropertyName<Car>(x => x.Model),
                // formattingEnabled is needed to avoid invalid cast
                // exceptions assigning the object property to the control:
                formattingEnabled: true));
        }
    }
}
Paul Smith
  • 3,104
  • 1
  • 32
  • 45
1

@abatishchev example only works if Wheels is a reference type.

If you have the following

public class Car
{
   public int ID;
}

And you try to call this

var prop = ReflectionHelper.PropertyName<Car>(x => x.ID);

You'll get the following exception

InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MemberExpression'.

I think this has to do with the fact that you are passing a value type to the expression, so it has to be boxed into an object. If you pass a reference type, it doesn't need to be boxed to object.

What you can do instead is this:

var prop = ReflectionHelper.PropertyName((Car x) => x.ID);

public static class ReflectionHelper
{
    public static string PropertyName<T, P>(Expression<Func<T, P>> property) 
        where T : class 
    {
        MemberExpression body = (MemberExpression)property.Body;
        return body.Member.Name;
    }
}
Pedro Faustino
  • 227
  • 3
  • 14
  • I added an answer below with an example of how to modify the PropertyName method so it can safely be used with the original syntax from @abatishchev’s answer. – Paul Smith Jul 16 '20 at 23:39