Linq expressions and extension methods to get property name

妖精的绣舞 提交于 2019-11-28 10:27:29

The following will return a property name as a string from a lambda expression:

public string PropertyName<TProperty>(Expression<Func<TProperty>> property)
{
  var lambda = (LambdaExpression)property;

  MemberExpression memberExpression;
  if (lambda.Body is UnaryExpression)
  {
    var unaryExpression = (UnaryExpression)lambda.Body;
    memberExpression = (MemberExpression)unaryExpression.Operand;
  }
  else
  {
    memberExpression = (MemberExpression)lambda.Body;
  }

  return memberExpression.Member.Name;
}

Usage:

public class MyClass
{
  public int World { get; set; }
}

...
var c = new MyClass();
Console.WriteLine("Hello {0}", PropertyName(() => c.World));

UPDATE

public static class Extensions
{
    public static void Bind<TSourceProperty, TDestinationProperty>(this INotifyPropertyChanged source, Expression<Func<TSourceProperty, TDestinationProperty>> bindExpression)
    {
        var expressionDetails = GetExpressionDetails<TSourceProperty, TDestinationProperty>(bindExpression);
        var sourcePropertyName = expressionDetails.Item1;
        var destinationObject = expressionDetails.Item2;
        var destinationPropertyName = expressionDetails.Item3;

        // Do binding here
        Console.WriteLine("{0} {1}", sourcePropertyName, destinationPropertyName);
    }

    private static Tuple<string, INotifyPropertyChanged, string> GetExpressionDetails<TSourceProperty, TDestinationProperty>(Expression<Func<TSourceProperty, TDestinationProperty>> bindExpression)
    {
        var lambda = (LambdaExpression)bindExpression;

        ParameterExpression sourceExpression = lambda.Parameters.FirstOrDefault();
        MemberExpression destinationExpression = (MemberExpression)lambda.Body;

        var memberExpression = destinationExpression.Expression as MemberExpression;
        var constantExpression = memberExpression.Expression as ConstantExpression;
        var fieldInfo = memberExpression.Member as FieldInfo;
        var destinationObject = fieldInfo.GetValue(constantExpression.Value) as INotifyPropertyChanged;

        return new Tuple<string, INotifyPropertyChanged, string>(sourceExpression.Name, destinationObject, destinationExpression.Member.Name);
    }
}

Usage:

public class TestSource : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Name { get; set; }        
}

public class TestDestination : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Id { get; set; }    
}

class Program
{        
    static void Main(string[] args)
    {
        var x = new TestSource();
        var y = new TestDestination();

        x.Bind<string, string>(Name => y.Id);
    }    
}
drzaus

This question is very similar to: Retrieving Property name from lambda expression

(Cross-posting answer from https://stackoverflow.com/a/17220748/1037948)

I don't know if you need to bind to "subproperties", but inspecting the lambda.Body for Member.Name will only return the "final" property, not a "fully-qualified" property.

ex) o => o.Thing1.Thing2 would result in Thing2, not Thing1.Thing2.

This is problematic when trying to use this method to simplify EntityFramework DbSet.Include(string) with expression overloads.

So you can "cheat" and parse the Expression.ToString instead. Performance seemed comparable in my tests, so please correct me if this is a bad idea.

The Extension Method

/// <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</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

(Checking for the delimiter might even be overkill)

This is likely more than or not exactly what you asked for but I've done something similar to handle mapping of a property between two objects:

public interface IModelViewPropagationItem<M, V>
    where M : BaseModel
    where V : IView
{
    void SyncToView(M model, V view);
    void SyncToModel(M model, V view);
}

public class ModelViewPropagationItem<M, V, T> : IModelViewPropagationItem<M, V>
    where M : BaseModel
    where V : IView
{
    private delegate void VoidDelegate();

    public Func<M, T> ModelValueGetter { get; private set; }
    public Action<M, T> ModelValueSetter { get; private set; }
    public Func<V, T> ViewValueGetter { get; private set; }
    public Action<V, T> ViewValueSetter { get; private set; }

    public ModelViewPropagationItem(Func<M, T> modelValueGetter, Action<V, T> viewValueSetter)
        : this(modelValueGetter, null, null, viewValueSetter)
    { }

    public ModelViewPropagationItem(Action<M, T> modelValueSetter, Func<V, T> viewValueGetter)
        : this(null, modelValueSetter, viewValueGetter, null)
    { }

    public ModelViewPropagationItem(Func<M, T> modelValueGetter, Action<M, T> modelValueSetter, Func<V, T> viewValueGetter, Action<V, T> viewValueSetter)
    {
        this.ModelValueGetter = modelValueGetter;
        this.ModelValueSetter = modelValueSetter;
        this.ViewValueGetter = viewValueGetter;
        this.ViewValueSetter = viewValueSetter;
    }

    public void SyncToView(M model, V view)
    {
        if (this.ViewValueSetter == null || this.ModelValueGetter == null)
            throw new InvalidOperationException("Syncing to View is not supported for this instance.");

        this.ViewValueSetter(view, this.ModelValueGetter(model));
    }

    public void SyncToModel(M model, V view)
    {
        if (this.ModelValueSetter == null || this.ViewValueGetter == null)
            throw new InvalidOperationException("Syncing to Model is not supported for this instance.");

        this.ModelValueSetter(model, this.ViewValueGetter(view));
    }
}

This allows you to create an instance of this object and then use "SyncToModel" and "SyncToView" to move values back and forth. The following piece that goes with this allows you to group multiple of these things and move data back and forth with one call:

public class ModelViewPropagationGroup<M, V> : List<IModelViewPropagationItem<M, V>>
    where M : BaseModel
    where V : IView
{
    public ModelViewPropagationGroup(params IModelViewPropagationItem<M, V>[] items)
    {
        this.AddRange(items);
    }

    public void SyncAllToView(M model, V view)
    {
        this.ForEach(o => o.SyncToView(model, view));
    }

    public void SyncAllToModel(M model, V view)
    {
        this.ForEach(o => o.SyncToModel(model, view));
    }
}

Usage would look something like this:

private static readonly ModelViewPropagationItem<LoginModel, ILoginView, string> UsernamePI = new ModelViewPropagationItem<LoginModel, ILoginView, string>(m => m.Username.Value, (m, x) => m.Username.Value = x, v => v.Username, (v, x) => v.Username = x);
private static readonly ModelViewPropagationItem<LoginModel, ILoginView, string> PasswordPI = new ModelViewPropagationItem<LoginModel, ILoginView, string>(m => m.Password.Value, (m, x) => m.Password.Value = x, v => v.Password, (v, x) => v.Password = x);
private static readonly ModelViewPropagationGroup<LoginModel, ILoginView> GeneralPG = new ModelViewPropagationGroup<LoginModel, ILoginView>(UsernamePI, PasswordPI);

public UserPrincipal Login_Click()
{
    GeneralPG.SyncAllToModel(this.Model, this.View);

    return this.Model.DoLogin();
}

Hope this helps!

var pr = typeof(CCategory).GetProperties().Select(i => i.Name).ToList(); ;

declaration:

    class Foo<T> {
            public string Bar<T, TResult>(Expression<Func<T, TResult>> expersion)
            {
                var lambda = (LambdaExpression)expersion;
                MemberExpression memberExpression;
                if (lambda.Body is UnaryExpression)
                {
                    var unaryExpression = (UnaryExpression)lambda.Body;
                    memberExpression = (MemberExpression)unaryExpression.Operand;
                }
                else
                {
                    memberExpression = (MemberExpression)lambda.Body;
                }

                return memberExpression.Member.Name;
            }
    }

Usage:

    var foo = new Foo<DummyType>();
    var propName = foo.Bar(d=>d.DummyProperty)
    Console.WriteLine(propName); //write "DummyProperty" string in shell
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!