Skip to content

Instantly share code, notes, and snippets.

@zaus
Last active October 2, 2021 20:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save zaus/6992590 to your computer and use it in GitHub Desktop.
Save zaus/6992590 to your computer and use it in GitHub Desktop.
From StackOverflow discussion http://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression/17220748#17220748 -- demonstrating the behavior of various solutions via LinqPad to answer questions in the comments. Usage: LinqPad as "C# Program".
void Main()
{
// from comment discussion http://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression/17220748#17220748
Expression<Func<O, int>> exprId = o => o.Id;
Expression<Func<O, string>> exprName = o => o.Name;
Expression<Func<O, int[]>> exprItems = o => o.Items;
Expression<Func<O, int>> exprItemsLength = o => o.Items.Length;
Expression<Func<O, Subclass>> exprChild = o => o.Child;
Expression<Func<O, string>> exprChildName = o => o.Child.Name;
Expression<Func<O, Subclass[]>> exprChildren = o => o.Children;
Expression<Func<O, int[]>> exprChildrenIds = o => o.Children.Select(c => c.Id).ToArray();
exprId.GetPropertyName().Dump(exprId.ToString());
exprName.GetPropertyName().Dump(exprName.ToString());
exprItems.GetPropertyName().Dump(exprItems.ToString());
exprItemsLength.GetPropertyName().Dump(exprItemsLength.ToString());
exprChild.GetPropertyName().Dump(exprChild.ToString());
exprChildName.GetPropertyName().Dump(exprChildName.ToString());
exprChildren.GetPropertyName().Dump(exprChildren.ToString());
exprChildrenIds.GetPropertyName().Dump(exprChildrenIds.ToString());
}
// Define other methods and classes here
public class O {
public int Id;
public string Name;
public int[] Items;
public Subclass Child;
public Subclass[] Children;
public O() { }
}
public class Subclass {
public int Id;
public string Name;
}
public static class ExprExt {
/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via http://stackoverflow.com/a/16647343/1037948
/// </summary>
/// <remarks>Cheats and uses the tostring output -- Should consult performance differences</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <param name="delimiter">Expression toString delimiter to split from lambda param</param>
/// <param name="endTrim">Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end</pa ram >
/// <returns>indicated property name</returns>
public static string GetPropertyName<TModel, TValue>(this Expression<Func<TModel, TValue>> propertySelector, char delimiter = '.', char endTrim = ')') {
var asString = propertySelector.ToString(); // gives you: "o => o.Whatever"
var firstDelim = asString.IndexOf(delimiter); // make sure there is a beginning property indicator; the "." in "o.Whatever" -- this may not be necessary?
return firstDelim < 0
? asString
: asString.Substring(firstDelim+1).TrimEnd(endTrim);
}//-- fn GetPropertyNameExtended
public static string GetName(Expression<Func<object>> exp)
{
MemberExpression body = exp.Body as MemberExpression;
if (body == null) {
UnaryExpression ubody = (UnaryExpression)exp.Body;
body = ubody.Operand as MemberExpression;
}
return body.Member.Name;
}
}
void Main()
{
// list of expressions to test against
var expressions = buildExpressionTestList();
// now performance
IEnumerable<string> results;
"Original Method".Perf(i => {
results = expressions.Select(o => GetPropertyName(o));
});
"Member Method".Perf(i => {
results = expressions.Select(o => GetPropertyNameFromMemberString(o));
});
"ToString Method".Perf(i => {
results = expressions.Select(o => GetPropertyNameFromString(o));
});
// do the methods work as expected?
var originalResult = expressions.Select(o => GetPropertyName(o));
var memberResult = expressions.Select(o => GetPropertyNameFromMemberString(o));
var stringResult = expressions.Select(o => GetPropertyNameFromString(o));
originalResult.Dump("Original Method");
memberResult.Dump("Member Method");
stringResult.Dump("ToString Method");
if( !memberResult.Except(originalResult).Any() ) throw new Exception("Should end up with differences between the two methods");
if( memberResult.Except(stringResult).Any() ) throw new Exception("Should NOT end up with differences between the two methods");
}
// Define other methods and classes here
protected class Foo {
public Guid ID { get; set; }
public string Name { get; set; }
public Bar FooBar { get; set; }
}
protected class Bar {
public int IDEN { get; set; }
public string Classification { get; set; }
public ICollection<Bar> FooBars { get; set; }
}
private static List<Expression<Func<Foo, object>>> buildExpressionTestList() {
var expressions = new List<Expression<Func<Foo, object>>> {
(o) => o.ID
,
(o) => o.Name
,
(o) => o.FooBar
,
(o) => o.FooBar.IDEN
,
(o) => o.FooBar.Classification
//, (o) => "frankenstein"
};
return expressions;
}
/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via http://stackoverflow.com/a/16647343/1037948
/// </summary>
/// <remarks>Just gives the last bit of the puzzle (i.e. when o => o.Name, returns "Name"; but if o => o.Foo.Bar.Name, only returns "Name")</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <returns>indicated property name</returns>
public static string GetPropertyName<TModel, TValue>(Expression<Func<TModel, TValue>> propertySelector) {
var body = propertySelector.Body as MemberExpression;
if (body == null) {
var ubody = (UnaryExpression)propertySelector.Body;
body = ubody.Operand as MemberExpression;
if (body == null) {
throw new ArgumentException("Could not get property name", "propertySelector");
}
}
return body.Member.Name;
}//-- fn GetPropertyName
// Define other methods and classes here
/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via http://stackoverflow.com/a/16647343/1037948
/// </summary>
/// <remarks>Just gives the last bit of the puzzle (i.e. when o => o.Name, returns "Name"; but if o => o.Foo.Bar.Name, only returns "Name")</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <param name="delimiter">Expression toString delimiter to split from lambda param</param>
/// <returns>indicated property name</returns>
public static string GetPropertyNameFromMemberString<TModel, TValue>(Expression<Func<TModel, TValue>> propertySelector, char delimiter = '.') {
var body = propertySelector.Body as MemberExpression;
if (body == null) {
var ubody = (UnaryExpression)propertySelector.Body;
body = ubody.Operand as MemberExpression;
if (body == null) {
throw new ArgumentException("Could not get property name", "propertySelector");
}
}
var asString = body.ToString();
var firstDelim = asString.IndexOf(delimiter);
return firstDelim < 0
? asString
: asString.Substring(firstDelim + 1);
}//-- fn GetPropertyNameFromMemberString
/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via http://stackoverflow.com/a/16647343/1037948
/// </summary>
/// <remarks>Cheats and uses the tostring output -- Should consult performance differences</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <param name="delimiter">Expression toString delimiter to split from lambda param</param>
/// <param name="endTrim">Sometimes the Expression toString contains something like a "Convert" operation, so we need to strip the closing part from the end</param>
/// <returns>indicated property name</returns>
public static string GetPropertyNameFromString<TModel, TValue>(Expression<Func<TModel, TValue>> propertySelector, char delimiter = '.', char endTrim = ')') {
var asString = propertySelector.ToString();
var firstDelim = asString.IndexOf(delimiter);
return firstDelim < 0
? asString
: asString.Substring(firstDelim + 1).TrimEnd(endTrim);
}//-- fn GetPropertyNameFromString
public static class MyExtensions {
/// <summary> Performance check -- how long do X repetitions of a task take?</summary>
public static long Perf(this string reportTitle, Action<int> task, int repetitions = 10000) {
// http://stackoverflow.com/questions/28637/is-datetime-now-the-best-way-to-measure-a-functions-performance
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < repetitions; i++) {
task(i);
}
sw.Stop();
string.Format("{0} ticks elapsed ({1} ms)", sw.Elapsed.Ticks, sw.Elapsed.TotalMilliseconds).Dump(string.Format("{0} ({1}x)", reportTitle, repetitions));
return sw.Elapsed.Ticks;
}
}

o => o.Id

Id

o => o.Name

Name

o => o.Items

Items

o => ArrayLength(o.Items)

Items

o => o.Child

Child

o => o.Child.Name

Child.Name

o => o.Children

Children

o => o.Children.Select(c => c.Id).ToArray()

Children.Select(c => c.Id).ToArray(

PERF

Original Method (10000x)

8981 ticks elapsed (0.8981 ms)

Member Method (10000x)

8879 ticks elapsed (0.8879 ms)

ToString Method (10000x)

8678 ticks elapsed (0.8678 ms)

OUTPUT

Original Method

IEnumerable (5 items)

  • ID
  • Name
  • FooBar
  • IDEN
  • Classification

Member Method

IEnumerable (5 items)

  • ID
  • Name
  • FooBar
  • FooBar.IDEN
  • FooBar.Classification

ToString Method

IEnumerable (5 items)

  • ID
  • Name
  • FooBar
  • FooBar.IDEN
  • FooBar.Classification
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment