取得物件名からのラムダ式
-
21-08-2019 - |
質問
あれをプロパティ名が渡されたよlambda。こちらはなんとしています。
例えば.
GetSortingInfo<User>(u => u.UserId);
これにより鋳造すmemberexpressionの場合のみ件は文字列です。ませんので、全てのプロパティに文字列を使いいただけるオブジェクトがその返unaryexpressionめます。
public static RouteValueDictionary GetInfo<T>(this HtmlHelper html,
Expression<Func<T, object>> action) where T : class
{
var expression = GetMemberInfo(action);
string name = expression.Member.Name;
return GetInfo(html, name);
}
private static MemberExpression GetMemberInfo(Expression method)
{
LambdaExpression lambda = method as LambdaExpression;
if (lambda == null)
throw new ArgumentNullException("method");
MemberExpression memberExpr = null;
if (lambda.Body.NodeType == ExpressionType.Convert)
{
memberExpr =
((UnaryExpression)lambda.Body).Operand as MemberExpression;
}
else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
{
memberExpr = lambda.Body as MemberExpression;
}
if (memberExpr == null)
throw new ArgumentException("method");
return memberExpr;
}
解決 2
私はあなたがそれを行うことができます別の方法は、ソースとプロパティが強く型付けされており、明示的にラムダの入力を推測することがわかりました。それが正しい用語であるが、ここでの結果であるかどうかわからない。
public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
var expression = (MemberExpression)action.Body;
string name = expression.Member.Name;
return GetInfo(html, name);
}
そして、そのようにそれを呼び出します。
GetInfo((User u) => u.UserId);
と出来上がり、それが動作します。
おかげで全てます。
他のヒント
私は最近、タイプセーフOnPropertyChangedを方法を作るために非常によく似たことをした。
ここでの発現のためにPropertyInfoオブジェクトを返します方法です。式はプロパティではありません場合は例外をスローします。
public PropertyInfo GetPropertyInfo<TSource, TProperty>(
TSource source,
Expression<Func<TSource, TProperty>> propertyLambda)
{
Type type = typeof(TSource);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
return propInfo;
}
コンパイラは、メソッド呼び出しに型推論を行うことができますので、 source
パラメータが使用されています。あなたは、次の操作を行うことができます。
var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
私は同じことで遊んで、これを後処理しました。それは完全にテストが、値型の問題(あなたがに走ったunaryexpression号)
を扱うように見えるていません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;
}
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}
このメンバーと単項表現を処理します。違いは、あなたの表現が参照型を表す場合は、あなたがUnaryExpression
を取得するのに対し、あなたの式が値型を表す場合は、MemberExpression
を取得することであること。すべてがオブジェクトにキャストすることができますが、値型はボックス化されなければなりません。 UnaryExpressionが存在する理由です。 参照。に
可読性(@Jowen)の酒のために、ここでの拡大と等価です
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
if (object.Equals(Field, null))
{
throw new NullReferenceException("Field is required");
}
MemberExpression expr = null;
if (Field.Body is MemberExpression)
{
expr = (MemberExpression)Field.Body;
}
else if (Field.Body is UnaryExpression)
{
expr = (MemberExpression)((UnaryExpression)Field.Body).Operand;
}
else
{
const string Format = "Expression '{0}' not supported.";
string message = string.Format(Format, Field);
throw new ArgumentException(message, "Field");
}
return expr.Member.Name;
}
今C#6には、単にこのnameof(User.UserId)
ようがNameOf を使用することができます
その中で、多くの利点を有しているが、これは時間にコンパイルhref="https://stackoverflow.com/a/26573179/57883">
C#7のパターンマッチングと
public static string GetMemberName<T>(this Expression<T> expression)
{
switch (expression.Body)
{
case MemberExpression m:
return m.Member.Name;
case UnaryExpression u when u.Operand is MemberExpression m:
return m.Member.Name;
default:
throw new NotImplementedException(expression.GetType().ToString());
}
}
例:
public static RouteValueDictionary GetInfo<T>(this HtmlHelper html,
Expression<Func<T, object>> action) where T : class
{
var name = action.GetMemberName();
return GetInfo(html, name);
}
これは一般的な実装では、文字列名分野/物/インデクサに最適方法の拡張手法/代表者のstruct/クラスまたはインタフェース/delegate/配列になります。い試験を組み合わせの静的/インスタンスは、非一般/一般に変更されることもあります。
//involves recursion
public static string GetMemberName(this LambdaExpression memberSelector)
{
Func<Expression, string> nameSelector = null; //recursive func
nameSelector = e => //or move the entire thing to a separate recursive method
{
switch (e.NodeType)
{
case ExpressionType.Parameter:
return ((ParameterExpression)e).Name;
case ExpressionType.MemberAccess:
return ((MemberExpression)e).Member.Name;
case ExpressionType.Call:
return ((MethodCallExpression)e).Method.Name;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
return nameSelector(((UnaryExpression)e).Operand);
case ExpressionType.Invoke:
return nameSelector(((InvocationExpression)e).Expression);
case ExpressionType.ArrayLength:
return "Length";
default:
throw new Exception("not a proper member selector");
}
};
return nameSelector(memberSelector.Body);
}
このことが書かれた簡単な while
ループによっ
//iteration based
public static string GetMemberName(this LambdaExpression memberSelector)
{
var currentExpression = memberSelector.Body;
while (true)
{
switch (currentExpression.NodeType)
{
case ExpressionType.Parameter:
return ((ParameterExpression)currentExpression).Name;
case ExpressionType.MemberAccess:
return ((MemberExpression)currentExpression).Member.Name;
case ExpressionType.Call:
return ((MethodCallExpression)currentExpression).Method.Name;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
currentExpression = ((UnaryExpression)currentExpression).Operand;
break;
case ExpressionType.Invoke:
currentExpression = ((InvocationExpression)currentExpression).Expression;
break;
case ExpressionType.ArrayLength:
return "Length";
default:
throw new Exception("not a proper member selector");
}
}
}
私のように再帰的アプローチのものも読みやすくなりました。きのように:
someExpr = x => x.Property.ExtensionMethod()[0]; //or
someExpr = x => Static.Method().Field; //or
someExpr = x => VoidMethod(); //or
someExpr = () => localVariable; //or
someExpr = x => x; //or
someExpr = x => (Type)x; //or
someExpr = () => Array[0].Delegate(null); //etc
string name = someExpr.GetMemberName();
印刷最後ます。
注意:
場合の連鎖のように表現
A.B.C
, "C"が返されます。ことになりません
const
s,配列インデクサに最適又はenum
s(不可能な全てをカバーしている。
Array
.Lengthに来るとき、エッジケースがあります。 「長さが」プロパティとして公開されている間、あなたが以前に提案した解決策のいずれかでそれを使用することはできません。
using Contract = System.Diagnostics.Contracts.Contract;
using Exprs = System.Linq.Expressions;
static string PropertyNameFromMemberExpr(Exprs.MemberExpression expr)
{
return expr.Member.Name;
}
static string PropertyNameFromUnaryExpr(Exprs.UnaryExpression expr)
{
if (expr.NodeType == Exprs.ExpressionType.ArrayLength)
return "Length";
var mem_expr = expr.Operand as Exprs.MemberExpression;
return PropertyNameFromMemberExpr(mem_expr);
}
static string PropertyNameFromLambdaExpr(Exprs.LambdaExpression expr)
{
if (expr.Body is Exprs.MemberExpression) return PropertyNameFromMemberExpr(expr.Body as Exprs.MemberExpression);
else if (expr.Body is Exprs.UnaryExpression) return PropertyNameFromUnaryExpr(expr.Body as Exprs.UnaryExpression);
throw new NotSupportedException();
}
public static string PropertyNameFromExpr<TProp>(Exprs.Expression<Func<TProp>> expr)
{
Contract.Requires<ArgumentNullException>(expr != null);
Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);
return PropertyNameFromLambdaExpr(expr);
}
public static string PropertyNameFromExpr<T, TProp>(Exprs.Expression<Func<T, TProp>> expr)
{
Contract.Requires<ArgumentNullException>(expr != null);
Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);
return PropertyNameFromLambdaExpr(expr);
}
今すぐ使用例:
int[] someArray = new int[1];
Console.WriteLine(PropertyNameFromExpr( () => someArray.Length ));
PropertyNameFromUnaryExpr
は、このように、コンパイラもデバッグで、最適化として、バッキング長のフィールドのへの直接アクセスを生成するようだ(「someArray」はコンソールに出力されるだろう、ArrayLength
をチェックしなかった場合特殊なケース)。
の一部がダウンMemberExpression
/ UnaryExpression
にネストされたキャプチャしないドリルこの答えを示唆したことを、私を見つけました/サブプロパティます。
EX)o => o.Thing1.Thing2
むしろThing1
よりThing1.Thing2
返す。
DbSet.Include(...)
で動作するようにしようとしている場合は、この区別は重要です。
私はちょうどExpression.ToString()
を解析すると、正常に動作するようことを見出し、比較的迅速きました。私はUnaryExpression
バージョンに対してそれを比較して、さらにそれが速かったかどうかを確認するためにToString
のオフMember/UnaryExpression
を取得し、その差はごくわずかでした。これはひどいアイデアですなら、私を修正してください。
拡張メソッド
/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via https://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</param>
/// <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
(でもやり過ぎかもしれない区切り文字のチェック)の
デモ(LinqPad)
デモ+比較コード - https://gist.github.com/zaus/6992590
I "M )C#6を標的とするものを
のpublic static class MiscExtentions
{
public static string NameOf<TModel, TProperty>(this object @object, Expression<Func<TModel, TProperty>> propertyExpression)
{
var expression = propertyExpression.Body as MemberExpression;
if (expression == null)
{
throw new ArgumentException("Expression is not a property.");
}
return expression.Member.Name;
}
}
、私は好きで、それを呼び出します:
public class MyClass
{
public int Property1 { get; set; }
public string Property2 { get; set; }
public int[] Property3 { get; set; }
public Subclass Property4 { get; set; }
public Subclass[] Property5 { get; set; }
}
public class Subclass
{
public int PropertyA { get; set; }
public string PropertyB { get; set; }
}
// result is Property1
this.NameOf((MyClass o) => o.Property1);
// result is Property2
this.NameOf((MyClass o) => o.Property2);
// result is Property3
this.NameOf((MyClass o) => o.Property3);
// result is Property4
this.NameOf((MyClass o) => o.Property4);
// result is PropertyB
this.NameOf((MyClass o) => o.Property4.PropertyB);
// result is Property5
this.NameOf((MyClass o) => o.Property5);
これは、フィールドとプロパティの両方で正常に動作します。
もありません電話をする必要があり .Name.ToString()
, が広いですね。の配慮に必要がある場合がありますか x.Foo.Bar
返さなければな"Foo"は、"バー"、または例外となさせていただきます。繰り返し処理を実行します。
(再コメント)よりフレキシブルなソート、 こちらの.
Iは、デフォルトの方法は、文字列のみを受け入れるため、型安全な方法で修正された(Entity FrameworkのPOCOクラスの)フラグ特性にできるようにするObjectStateEntryに拡張メソッドを作成しました。ここでのプロパティから名前を取得する私の方法:
public static void SetModifiedProperty<T>(this System.Data.Objects.ObjectStateEntry state, Expression<Func<T>> action)
{
var body = (MemberExpression)action.Body;
string propertyName = body.Member.Name;
state.SetModifiedProperty(propertyName);
}
私は、以下の方法に類似INotifyPropertyChanged
の実装を行っています。ここで特性は、以下に示す基本クラスで辞書に格納されています。必ずしも、もちろん継承を使用することが望ましいが、ビューモデルのために私はそれが許容され、ビューモデルクラスで非常にきれいなプロパティ参照を与えると思います。
public class PhotoDetailsViewModel
: PropertyChangedNotifierBase<PhotoDetailsViewModel>
{
public bool IsLoading
{
get { return GetValue(x => x.IsLoading); }
set { SetPropertyValue(x => x.IsLoading, value); }
}
public string PendingOperation
{
get { return GetValue(x => x.PendingOperation); }
set { SetPropertyValue(x => x.PendingOperation, value); }
}
public PhotoViewModel Photo
{
get { return GetValue(x => x.Photo); }
set { SetPropertyValue(x => x.Photo, value); }
}
}
幾分より複雑な基底クラスを以下に示します。これは、プロパティ名にラムダ式からの変換を処理します。名前だけが使用されているので、プロパティが実際に擬似的特性であることに注意してください。しかし、それは、ビューモデルとビューモデルのプロパティへの参照には透過表示されます。
public class PropertyChangedNotifierBase<T> : INotifyPropertyChanged
{
readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
protected U GetValue<U>(Expression<Func<T, U>> property)
{
var propertyName = GetPropertyName(property);
return GetValue<U>(propertyName);
}
private U GetValue<U>(string propertyName)
{
object value;
if (!_properties.TryGetValue(propertyName, out value))
{
return default(U);
}
return (U)value;
}
protected void SetPropertyValue<U>(Expression<Func<T, U>> property, U value)
{
var propertyName = GetPropertyName(property);
var oldValue = GetValue<U>(propertyName);
if (Object.ReferenceEquals(oldValue, value))
{
return;
}
_properties[propertyName] = value;
RaisePropertyChangedEvent(propertyName);
}
protected void RaisePropertyChangedEvent<U>(Expression<Func<T, U>> property)
{
var name = GetPropertyName(property);
RaisePropertyChangedEvent(name);
}
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private static string GetPropertyName<U>(Expression<Func<T, U>> property)
{
if (property == null)
{
throw new NullReferenceException("property");
}
var lambda = property as LambdaExpression;
var memberAssignment = (MemberExpression) lambda.Body;
return memberAssignment.Member.Name;
}
public event PropertyChangedEventHandler PropertyChanged;
}
これは、別の答えは次のとおりです。
public static string GetPropertyName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
return metaData.PropertyName;
}
私はこの機能を残します:
/// <summary>
/// Get properties separated by , (Ex: to invoke 'd => new { d.FirstName, d.LastName }')
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="exp"></param>
/// <returns></returns>
public static string GetFields<T>(Expression<Func<T, object>> exp)
{
MemberExpression body = exp.Body as MemberExpression;
var fields = new List<string>();
if (body == null)
{
NewExpression ubody = exp.Body as NewExpression;
if (ubody != null)
foreach (var arg in ubody.Arguments)
{
fields.Add((arg as MemberExpression).Member.Name);
}
}
return string.Join(",", fields);
}
ここでは、PropertyInfoオフに基づいて取得する別の方法です。これは、オブジェクトインスタンスの必要性を排除しを。
/// <summary>
/// Get metadata of property referenced by expression. Type constrained.
/// </summary>
public static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
{
return GetPropertyInfo((LambdaExpression) propertyLambda);
}
/// <summary>
/// Get metadata of property referenced by expression.
/// </summary>
public static PropertyInfo GetPropertyInfo(LambdaExpression propertyLambda)
{
// https://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
if(propertyLambda.Parameters.Count() == 0)
throw new ArgumentException(String.Format(
"Expression '{0}' does not have any parameters. A property expression needs to have at least 1 parameter.",
propertyLambda.ToString()));
var type = propertyLambda.Parameters[0].Type;
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(String.Format(
"Expression '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
return propInfo;
}
それはそうのように呼び出すことができます
var propertyInfo = GetPropertyInfo((User u) => u.UserID);
私はConvert
に対して、いくつかの安全性チェックを含めるように @キャメロンの答えを更新したラムダ式を入力:
PropertyInfo GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
var body = propertyLambda.Body;
if (!(body is MemberExpression member)
&& !(body is UnaryExpression unary
&& (member = unary.Operand as MemberExpression) != null))
throw new ArgumentException($"Expression '{propertyLambda}' " +
"does not refer to a property.");
if (!(member.Member is PropertyInfo propInfo))
throw new ArgumentException($"Expression '{propertyLambda}' " +
"refers to a field, not a property.");
var type = typeof(TSource);
if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
throw new ArgumentException($"Expresion '{propertyLambda}' " +
"refers to a property that is not from type '{type}'.");
return propInfo;
}
始ます。NET4.0を利用でき ExpressionVisitor
探し物件:
class ExprVisitor : ExpressionVisitor {
public bool IsFound { get; private set; }
public string MemberName { get; private set; }
public Type MemberType { get; private set; }
protected override Expression VisitMember(MemberExpression node) {
if (!IsFound && node.Member.MemberType == MemberTypes.Property) {
IsFound = true;
MemberName = node.Member.Name;
MemberType = node.Type;
}
return base.VisitMember(node);
}
}
皆様からのあたたかいご)をご利用のお客様:
var visitor = new ExprVisitor();
visitor.Visit(expr);
if (visitor.IsFound) {
Console.WriteLine("First property in the expression tree: Name={0}, Type={1}", visitor.MemberName, visitor.MemberType.FullName);
} else {
Console.WriteLine("No properties found.");
}