48

nameof(order.User.Age) return only Age instead of order.User.Age

What is the reason to do it in more restricted way? If we want only last name we could do something like

public static GetLastName(this string x) { 
    return string.Split(x, '.').Last();
}

nameof(order.User.Age).GetLastName()

And with one operator we could get both, Age and order.User.Age. But with current implementation we can only get Age. Is there some logic behind this decision? For example, such behavior is necessary for MVC binding

Html.TextBox(nameof(order.User.Age))
Vy Do
  • 46,709
  • 59
  • 215
  • 313
ais
  • 2,514
  • 2
  • 17
  • 24

6 Answers6

28

Note that if you need/want the "full" name, you could do this:

$"{nameof(order)}.{nameof(User)}.{nameof(Age)}".GetLastName();

as long as all of these names are in the current scope.

Obviously in this case it's not really all that helpful (the names won't be in scope in the Razor call), but it might be if you needed, for example, the full namespace qualified name of a type for a call to Type.GetType() or something.

If the names are not in scope, you could still do the somewhat more clunky:

$"{nameof(order)}.{nameof(order.User)}.{nameof(order.User.Age)}".GetLastName();

-- although chances are at least one of those should be in scope (unless User.Age is a static property).

Dan Field
  • 20,885
  • 5
  • 55
  • 71
  • 6
    This does not work since `nameof` needs to be fully qualified. It would thus rather look like `$"{nameof(order)}.{nameof(order.User)}.{nameof(order.User.Age)}"`, indicating exactly the point of the OP. – Steven Jeuris Mar 03 '17 at 11:50
  • 3
    This absolutely does work - nameof does not need to be fully qualified, the parameters to it just need to be in scope. I may have been assuming that all of those names were in scope, but that doesn't mean it doesn't work. Obviously if you're trying to use it in a different scope they do need to be qualified, but I don't see how that really affects the answer. – Dan Field Mar 03 '17 at 12:58
  • 1
    I might have phrased it incorrectly by stating 'fully qualified'. However, it does affect the answer since from my reading of the OP, they are _not_ all in scope. – Steven Jeuris Mar 03 '17 at 13:49
  • Yes, looking at the complete question from OP you're right - I'll edit my answer to specify... – Dan Field Mar 03 '17 at 13:51
  • 3
    how does `$"{nameof(order)}.{nameof(User)}.{nameof(Age)}".GetLastName();` make sense? For those properties to be "in scope" `order` would have to be `this` and `User` and `Age` would have to be properties on `this` which they aren't :/ This does not answer op's question. And what on earth is `GetLastName()` on a string? – Mardoxx Jun 16 '17 at 13:56
  • `order` and `User` could be namespace names. – Dan Field Jun 16 '17 at 15:43
  • 3
    Very unlikely would you not say so. – Mardoxx Jun 18 '17 at 02:11
  • I believe the first part of this answer is wrong. User is a property off order and Age is a property off User. So how would they be in scope? – Shawn Oct 23 '20 at 13:58
  • This is a pretty old answer at this point, but I think I edited it to clarify that it only works if all thenames are in scope, which is not likely in this particular example but could happen in other possible examples (such as when those names are namespace or type names). – Dan Field Oct 23 '20 at 19:49
  • This worked for the problem I was having. I was trying to generate a list of strings from entity names and I had to get one in the form of "ProjectStatus.ProjectStatus", so using `$"{nameof(ProjectStatus)}.{nameof(ProjectStatus)}"` worked for me – WTFranklin Dec 20 '22 at 21:44
11

I had the same problem and implemented a class that acts as a replacement of the nameof() keyword in order to get the full name of the expression being supplied. It's greatly inspired from OK HOSTING answer. It's just all baked and ready to use:

public static class NameOf<TSource>
{
    #region Public Methods

    public static string Full(Expression<Func<TSource, object>> expression)
    {
        var memberExpression = expression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = expression.Body as UnaryExpression;
            if (unaryExpression != null && unaryExpression.NodeType == ExpressionType.Convert)
                memberExpression = unaryExpression.Operand as MemberExpression;
        }

        var result = memberExpression.ToString();
        result = result.Substring(result.IndexOf('.') + 1);

        return result;
    }

    public static string Full(string sourceFieldName, Expression<Func<TSource, object>> expression)
    {
        var result = Full(expression);
        result = string.IsNullOrEmpty(sourceFieldName) ? result : sourceFieldName + "." + result;
        return result;
    }

    #endregion
}

Using it in your code would look like:

class SpeciesFamily
{
    public string Name { get; set; }
}

class Species
{
    public SpeciesFamily Family { get; set; }
    public string Name { get; set; }
}

class Cat
{
    public Species Species { get; set; }
}

// Will return a string containing "Species.Family.Name".
var fullName = NameOf<Cat>.Full(c => c.Species.Family.Name);

// Will return a string containing "cat.Species.Name".
var fullNameWithPrefix = NameOf<Cat>.Full("cat", c => c.Species.Name);
sboisse
  • 4,860
  • 3
  • 37
  • 48
  • 2
    If you pass an array to the expression. For example take this expression `phone => phone.Lines[1].AsteriskSipPeer` it will return `Lines.get_Item(1).AsteriskSipPeer`. I just added this regex to take care of that `result = Regex.Replace(result, @"(?x) get_Item \( (\d+) \)", m=> $"[{m.Groups[1].Value}]")` – Tono Nam Apr 30 '21 at 14:43
10

Because it is exactly what for it've been invented. As you can read in already linked discussions, here you using the nameof operator as nameof(member-access), of the form E.I<A1…AK>, which will return:

These cases are all resolved using the rules for simple name lookup $7.6.2 or member access $7.6.4. If they succeed in binding, they must bind to one of:

  • A method-group. This produces an error "To specify the name of a method, you must provide its arguments".
  • A variable, value, parameter, constant, enumeration-member, property-access, field, event, type-parameter, namespace or type. In this case the result of the nameof operator is simply "I", which is generally the name of the symbol that the argument bound to. There are some caveats…

So in this case it, by its definition, have to evaluate all expressions before all the dots, step by step, and after that evaluate the last one to get its Name:

order.User.Age --> User.Age --> Age
VMAtm
  • 27,943
  • 17
  • 79
  • 125
5

Take a look at this method taken from:

https://github.com/okhosting/OKHOSTING.Data/blob/master/src/PCL/OKHOSTING.Data/Validation/MemberExpression.cs

public static string GetMemberString(System.Linq.Expressions.Expression<Func<T, object>> member)
    {
        if (member == null)
        {
            throw new ArgumentNullException("member");
        }

        var propertyRefExpr = member.Body;
        var memberExpr = propertyRefExpr as System.Linq.Expressions.MemberExpression;

        if (memberExpr == null)
        {
            var unaryExpr = propertyRefExpr as System.Linq.Expressions.UnaryExpression;

            if (unaryExpr != null && unaryExpr.NodeType == System.Linq.Expressions.ExpressionType.Convert)
            {
                memberExpr = unaryExpr.Operand as System.Linq.Expressions.MemberExpression;

                if(memberExpr != null)
                {
                    return memberExpr.Member.Name;
                }
            }
        }
        else
        {
            //gets something line "m.Field1.Field2.Field3", from here we just remove the prefix "m."
            string body = member.Body.ToString();
            return body.Substring(body.IndexOf('.') + 1);
        }

        throw new ArgumentException("No property reference expression was found.", "member");
    }
OK HOSTING
  • 80
  • 1
  • 6
4

Some of the important purposes of using nameof is to get the last "name" in the expression.

For example nameof parameter when throwing ArgumentNullException:

void Method(string parameter)
{
     if (parameter == null) throw new ArgumentNullException(nameof(parameter));
}

MVC Action links

<%= Html.ActionLink("Sign up",
    @typeof(UserController),
    @nameof(UserController.SignUp))
%>

INotifyPropertyChanged

int p {
    get { return this._p; }
    set { this._p = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.p)); }
}

More information: https://roslyn.codeplex.com/discussions/570551

Nikolay Kostov
  • 16,433
  • 23
  • 85
  • 123
  • What about MVC binding Html.TextBox(nameof(User.Name))? – ais Jan 12 '15 at 09:03
  • 4
    Umm, "all purposes"? No, I would argue the opposite. For instance, when you encounter an unexpected null reference and want to log an error, but don't want to throw an exception, you'd like to give as much context as possible as to what exactly was null. Take this example: Instance.Window.TextLabel.TextField.StringLabel.Value.Reference.Object. Suppose "Object" is null. Which would be more useful for diagnostic?: Just "Object", or "Instance.Window.TextLabel.TextField.StringLabel.Value.Reference.Object"? – Hatchling May 10 '16 at 22:08
  • 2
    I'd say null is something different all together, but if you look at the bread and butter cases listed in the link, Logging is also there, building on that, your case would probably be logged like this: `MyMethod(InsaneTypeThatIsNull myVar) { Log(nameof(MyMethod), $"{nameof(myVar)} was null"); }` Its easy to backtrack this and find the type – Jim Wolff Sep 28 '16 at 11:33
  • @ais for MVC binding you should write an EditorTemplate for your User model and then use @Html.EditorFor(x => x.User) – Mik Oct 12 '18 at 13:00
0

.NET 6 added CallerArgumentExpression which lets you create a workaround version of fullnameof.

using System.Runtime.CompilerServices;

var fullString = StringOf(nameof(HttpResponseMessage.Content.Headers));

// Prints: HttpResponseMessage.Content.Headers
Console.WriteLine(fullString);

static string StringOf(string value, [CallerArgumentExpression(nameof(value))] string fullpath = default!)
{
    // value is: "value"
    // fullpath is: "nameof(HttpResponseMessage.Content.Headers)"
    // Do some validation here...

    // Strip "nameof(", ... ")"
    string outputString = fullpath.Substring(fullpath.IndexOf("(") + 1, fullpath.IndexOf(")") - fullpath.IndexOf("(") - 1);

    return outputString;
}
DaemonFire
  • 573
  • 6
  • 13