Workaround for lack of 'nameof' operator in C# for type-safe databinding?
Asked Answered
F

8

45

There has been a lot of sentiment to include a nameof operator in C#. As an example of how this operator would work, nameof(Customer.Name) would return the string "Name".

I have a domain object. And I have to bind it. And I need names of properties as strings then. And I want them to be type-safe.

I remember coming across a workaround in .NET 3.5 which provided the functionality of nameof and involved lambda expressions. However, I have not been able to locate this workaround. Can anyone provide that workaround to me?

I am also interested in a way to implement the functionality of nameof in .NET 2.0 if that is possible.

Frear answered 19/11, 2008 at 12:54 Comment(2)
see also #1329638Flabellate
This issue is now solved at compile time! The nameof operator was implemented in C# 6.0 with .NET 4.6 and VS2015 in July 2015. The following answers are still valid for C# < 6.0.Vibrato
M
78

This code basically does that:

class Program
{
    static void Main()
    {
        var propName = Nameof<SampleClass>.Property(e => e.Name);

        Console.WriteLine(propName);
    }
}

public class Nameof<T>
{
    public static string Property<TProp>(Expression<Func<T, TProp>> expression)
    {
        var body = expression.Body as MemberExpression;
        if(body == null)
            throw new ArgumentException("'expression' should be a member expression");
        return body.Member.Name;
    }
}

(Of course it is 3.5 code...)

Metalwork answered 19/11, 2008 at 14:3 Comment(2)
Please note that there is a performance penalty. Expression objects are quite expensive to create. Calling a Foo(Expression<Func<>>) is 200 times slower than an old fashioned Foo(string propName). Please vote for a compile-time nameof operator.Bursarial
Note that this code must have been compiled in VS2015 in order to use Expression.BodyMartel
A
6

While reshefm and Jon Skeet show the proper way to do this using expressions, it should be worth noting there's a cheaper way to do this for method names:

Wrap a delegate around your method, get the MethodInfo, and you're good to go. Here's an example:

private void FuncPoo()
{
}

...

// Get the name of the function
string funcName = new Action(FuncPoo).Method.Name;

Unfortunately, this works only for methods; it does not work for properties, as you cannot have delegates to property getter or setter methods. (Seems like a silly limitation, IMO.)

Ashanti answered 19/11, 2008 at 14:53 Comment(1)
I agree. Silly limitation. I don't think it should be too hard for the compiler to understand whether it is the getter or the setter and let u use delegate it.Metalwork
S
4

The workaround is to use an expression tree, and to take that expression tree apart to find the relevant MemberInfo. There's slightly more detail and comment in this note (although not the code to pull out the member - that's in another SO question somewhere, I believe).

Unfortunately as expression trees don't exist in .NET 2.0, there's really no equivalent.

One solution to avoid typos is to have a set of accessors which fetch the relevant PropertyInfo for a particular property, and unit test them. That would be the only place which had the string in it. This would avoid duplication and make refactoring easier, but it's a bit draconian.

Sportsman answered 19/11, 2008 at 12:57 Comment(1)
is this what you are looking for? imaginarydevelopment.blogspot.com/2009/10/… it references this #1329638Overset
M
4

An extension to what reshefm did, that simplified the usage of the nameof() operator, and gives the names of methods and class members and methods as well:

/// <summary>
/// Provides the <see cref="nameof"/> extension method that works as a workarounds for a nameof() operator, 
/// which should be added to C# sometime in the future.
/// </summary>
public static class NameOfHelper
{
    /// <summary>
    /// Returns a string represantaion of a property name (or a method name), which is given using a lambda expression.
    /// </summary>
    /// <typeparam name="T">The type of the <paramref name="obj"/> parameter.</typeparam>
    /// <typeparam name="TProp">The type of the property (or the method's return type), which is used in the <paramref name="expression"/> parameter.</typeparam>
    /// <param name="obj">An object, that has the property (or method), which its name is returned.</param>
    /// <param name="expression">A Lambda expression of this pattern: x => x.Property <BR/>
    /// Where the x is the <paramref name="obj"/> and the Property is the property symbol of x.<BR/>
    /// (For a method, use: x => x.Method()</param>
    /// <returns>A string that has the name of the given property (or method).</returns>
    public static string nameof<T, TProp>(this T obj, Expression<Func<T, TProp>> expression)
    {
        MemberExpression memberExp = expression.Body as MemberExpression;
        if (memberExp != null)
            return memberExp.Member.Name;

        MethodCallExpression methodExp = expression.Body as MethodCallExpression;
        if (methodExp != null)
            return methodExp.Method.Name;

        throw new ArgumentException("'expression' should be a member expression or a method call expression.", "expression");
    }

    /// <summary>
    /// Returns a string represantaion of a property name (or a method name), which is given using a lambda expression.
    /// </summary>
    /// <typeparam name="TProp">The type of the property (or the method's return type), which is used in the <paramref name="expression"/> parameter.</typeparam>
    /// <param name="expression">A Lambda expression of this pattern: () => x.Property <BR/>
    /// Where Property is the property symbol of x.<BR/>
    /// (For a method, use: () => x.Method()</param>
    /// <returns>A string that has the name of the given property (or method).</returns>
    public static string nameof<TProp>(Expression<Func<TProp>> expression)
    {
        MemberExpression memberExp = expression.Body as MemberExpression;
        if (memberExp != null)
            return memberExp.Member.Name;

        MethodCallExpression methodExp = expression.Body as MethodCallExpression;
        if (methodExp != null)
            return methodExp.Method.Name;

        throw new ArgumentException("'expression' should be a member expression or a method call expression.", "expression");
    }
}

To use it:

static class Program
{
    static void Main()
    {
        string strObj = null;
        Console.WriteLine(strObj.nameof(x => x.Length)); //gets the name of an object's property.
        Console.WriteLine(strObj.nameof(x => x.GetType())); //gets the name of an object's method.
        Console.WriteLine(NameOfHelper.nameof(() => string.Empty)); //gets the name of a class' property.
        Console.WriteLine(NameOfHelper.nameof(() => string.Copy(""))); //gets the name of a class' method.
    }
}
Mingy answered 11/1, 2010 at 12:0 Comment(0)
I
4

Unless someone changes their mind, the nameof operator looks like it's coming in C# 6. Here are the design meeting notes about it:

https://roslyn.codeplex.com/discussions/552376

https://roslyn.codeplex.com/discussions/552377

Initiate answered 28/9, 2014 at 13:56 Comment(2)
I've been waiting this for so long. So nice to see it being implemented.Buff
And here is the official documentation for the nameof operator which did indeed become available in C# 6.0 (and Visual Studio 2015).Frink
A
2

This is part of the language in C# 6.0

https://msdn.microsoft.com/en-us/magazine/dn802602.aspx

Accouchement answered 18/8, 2015 at 21:47 Comment(0)
B
2

The accepted solution is nice, simple and elegant.

However, building an expression tree is expensive, and I need the whole property path.

So I changed it a bit. It is not elegant at all, but it is simple and works well in most cases:

public static string Property<TProp>(Expression<Func<T, TProp>> expression)
{
    var s = expression.Body.ToString();
    var p = s.Remove(0, s.IndexOf('.') + 1);
    return p;
}

Example:

? Nameof<DataGridViewCell>.Property(c => c.Style.BackColor.A);
"Style.BackColor.A"
Bleeding answered 19/11, 2015 at 8:29 Comment(2)
I created the generic method: public static string PropertyPath<T,TProp>(this T obj, Expression<Func<T, TProp>> expression) { var s = expression.Body.ToString(); var p = s.Remove(0, s.IndexOf('.') + 1); return p; }Untrue
From the documentation for the nameof operator (new to C# 6.0), in the section Remarks: If you need to get the fully-qualified name, you can use the typeof expression along with nameof .Frink
L
1

The answer from reshefm is pretty good, but this is a little bit simpler API IMO:

Usage example: NameOf.Property(() => new Order().Status)

using System;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;

namespace AgileDesign.Utilities
{
public static class NameOf
{
    ///<summary>
    ///  Returns name of any method expression with any number of parameters either void or with a return value
    ///</summary>
    ///<param name = "expression">
    ///  Any method expression with any number of parameters either void or with a return value
    ///</param>
    ///<returns>
    ///  Name of any method with any number of parameters either void or with a return value
    ///</returns>
    [Pure]
    public static string Method(Expression<Action> expression)
    {
        Contract.Requires<ArgumentNullException>(expression != null);

        return ( (MethodCallExpression)expression.Body ).Method.Name;
    }

    ///<summary>
    ///  Returns name of property, field or parameter expression (of anything but method)
    ///</summary>
    ///<param name = "expression">
    ///  Property, field or parameter expression
    ///</param>
    ///<returns>
    ///  Name of property, field, parameter
    ///</returns>
    [Pure]
    public static string Member(Expression<Func<object>> expression)
    {
        Contract.Requires<ArgumentNullException>(expression != null);

        if(expression.Body is UnaryExpression)
        {
            return ((MemberExpression)((UnaryExpression)expression.Body).Operand).Member.Name;
        }
        return ((MemberExpression)expression.Body).Member.Name;
    }
  }
}

Full code is here: http://agiledesignutilities.codeplex.com/SourceControl/changeset/view/b76cefa4234a#GeneralPurpose/NameOf.cs

Leavenworth answered 18/2, 2013 at 3:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.