c# - महत - लिखित अभिव्यक्ति क्या है




लैम्ब्डा अभिव्यक्ति से संपत्ति का नाम पुनर्प्राप्त करना (12)

.NET 4.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.");
}

लैम्ब्डा अभिव्यक्ति के माध्यम से पारित होने पर संपत्ति का नाम प्राप्त करने का कोई बेहतर तरीका है? मेरे पास वर्तमान में क्या है।

जैसे।

GetSortingInfo<User>(u => u.UserId);

यह केवल एक memberexpression के रूप में कास्टिंग करके काम किया जब संपत्ति एक स्ट्रिंग था। क्योंकि सभी गुण स्ट्रिंग नहीं हैं, मुझे ऑब्जेक्ट का उपयोग करना था, लेकिन फिर यह उन लोगों के लिए एक अपरिवर्तनीयता वापस कर देगा।

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;
}

अब सी # 6 में आप बस इस nameof(User.UserId) तरह नाम का उपयोग कर सकते हैं nameof(User.UserId)

जिनमें से कई लाभ हैं, उनमें से यह है कि यह संकलन समय पर किया जाता है, रनटाइम नहीं।

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


जब Array तरफ आता है तो एक बढ़त का मामला है। जबकि 'लंबाई' को संपत्ति के रूप में उजागर किया जाता है, आप इसे पहले प्रस्तावित समाधानों में से किसी एक में उपयोग नहीं कर सकते हैं।

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 ने ArrayLength जांच नहीं की है, तो "someArray" को कंसोल पर मुद्रित किया जाएगा (कंपाइलर बैकिंग लम्बाई फ़ील्ड तक सीधे पहुंच प्रदान करता है, एक अनुकूलन के रूप में, यहां तक ​​कि डीबग में भी, विशेष मामले)।


फ़ील्ड / गुण / इंडेक्सर्स / विधियों / एक्सटेंशन विधियों / स्ट्रक्चर / क्लास / इंटरफ़ेस / प्रतिनिधि / सरणी के प्रतिनिधियों के स्ट्रिंग नाम प्राप्त करने के लिए यह एक सामान्य कार्यान्वयन है। मैंने स्थैतिक / उदाहरण और गैर-जेनेरिक / जेनेरिक रूपों के संयोजनों का परीक्षण किया है।

//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);
}

यह चीज एक साधारण लूप में भी लिखी जा सकती है:

//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();

अंतिम सदस्य मुद्रित करने के लिए।

ध्यान दें:

  1. ABC जैसे जंजीर अभिव्यक्तियों के मामले में, "सी" वापस आ गया है।

  2. यह const एस, सर इंडेक्सर्स या enum एस (सभी मामलों को कवर करने के लिए असंभव) के साथ काम नहीं करता है।


मैं सी # 6 को लक्षित करने वालों के लिए पूर्व सी # 6 परियोजनाओं और nameof लिए एक विस्तार विधि का उपयोग कर रहा हूं।

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);

यह दोनों क्षेत्रों और संपत्तियों के साथ ठीक काम करता है।


मैंने ऑब्जेक्टस्टेट एंटर्री पर एक विस्तार विधि बनाई है ताकि गुणों को एक प्रकार से सुरक्षित तरीके से संशोधित किया जा सके (एंटीटी फ्रेमवर्क पीओसीओ कक्षाओं) को फ़्लैग करने में सक्षम हो, क्योंकि डिफ़ॉल्ट विधि केवल एक स्ट्रिंग स्वीकार करती है। संपत्ति से नाम प्राप्त करने का मेरा तरीका यहां दिया गया है:

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 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);

और voila यह काम करता है।
सबको शुक्रीया।


मैंने हाल ही में एक प्रकार की सुरक्षित 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);

यदि आप गुणक फ़ील्ड प्राप्त करना चाहते हैं तो मैं इस फ़ंक्शन को छोड़ देता हूं:

/// <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);
    }

कैमरून द्वारा प्रस्तावित विधि के लिए यहां एक अद्यतन है। पहला पैरामीटर आवश्यक नहीं है।

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    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(
            "Expresion '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

आप निम्नलिखित कर सकते हैं:

var propertyInfo = GetPropertyInfo<SomeType>(u => u.UserID);
var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);

विस्तार विधियां:

public static PropertyInfo GetPropertyInfo<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    return GetPropertyInfo(propertyLambda);
}

public static string NameOfProperty<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    PropertyInfo prodInfo = GetPropertyInfo(propertyLambda);
    return prodInfo.Name;
}

आप ऐसा कर सकते हैं:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);

इस जवाब के आधार पर 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://.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);




expression-trees