Linq表达式和扩展方法获取属性名称

我正在看这篇文章,它描述了一种在POCO属性之间进行数据绑定的简单方法: 数据绑定POCO属性

Bevan的评论之一包括一个简单的Binder类,可用于完成此类数据绑定。 它适用于我需要的东西,但我想实施Bevan为改进课程所做的一些建议,即:

  • 检查是否已分配源和目标
  • 检查sourcePropertyName和targetPropertyName标识的属性是否存在
  • 检查两个属性之间的类型兼容性

此外,鉴于通过字符串指定属性容易出错,您可以使用Linq表达式和扩展方法。 然后而不是写作

Binder.Bind( source, "Name", target, "Name") 

你可以写

 source.Bind( Name => target.Name); 

我很确定我可以处理前三个(虽然可以随意包含这些更改)但我不知道如何使用Linq表达式和扩展方法来编写代码而不使用属性名称字符串。

有小费吗?

以下是链接中的原始代码:

 public static class Binder { public static void Bind( INotifyPropertyChanged source, string sourcePropertyName, INotifyPropertyChanged target, string targetPropertyName) { var sourceProperty = source.GetType().GetProperty(sourcePropertyName); var targetProperty = target.GetType().GetProperty(targetPropertyName); source.PropertyChanged += (s, a) => { var sourceValue = sourceProperty.GetValue(source, null); var targetValue = targetProperty.GetValue(target, null); if (!Object.Equals(sourceValue, targetValue)) { targetProperty.SetValue(target, sourceValue, null); } }; target.PropertyChanged += (s, a) => { var sourceValue = sourceProperty.GetValue(source, null); var targetValue = targetProperty.GetValue(target, null); if (!Object.Equals(sourceValue, targetValue)) { sourceProperty.SetValue(source, targetValue, null); } }; } } 

以下将从lambda表达式返回属性名称作为字符串:

 public string PropertyName(Expression> 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; } 

用法:

 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(this INotifyPropertyChanged source, Expression> bindExpression) { var expressionDetails = GetExpressionDetails(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 GetExpressionDetails(Expression> 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(sourceExpression.Name, destinationObject, destinationExpression.Member.Name); } } 

用法:

 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(Name => y.Id); } } 

这个问题非常类似于: 从lambda表达式中检索属性名称

(来自https://stackoverflow.com/a/17220748/1037948的交叉发布回复)

我不知道你是否需要绑定到“ lambda.Body ”,但是检查lambda.Body for Member.Name只会返回“final”属性,而不是“完全限定”属性。

ex) o => o.Thing1.Thing2会导致Thing2 ,而不是Thing1.Thing2

当尝试使用此方法来简化具有表达式重载的EntityFramework DbSet.Include(string)时,这会出现问题。

所以你可以“欺骗”并解析Expression.ToString 。 在我的测试中,性能似乎相当,所以如果这是一个坏主意,请纠正我。

扩展方法

 ///  /// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via https://stackoverflow.com/a/16647343/1037948 ///  /// Cheats and uses the tostring output -- Should consult performance differences /// the model type to extract property names /// the value type of the expected property /// expression that just selects a model property to be turned into a string /// Expression toString delimiter to split from lambda param /// Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end /// indicated property name public static string GetPropertyName(this Expression> 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 interface IModelViewPropagationItem where M : BaseModel where V : IView { void SyncToView(M model, V view); void SyncToModel(M model, V view); } public class ModelViewPropagationItem : IModelViewPropagationItem where M : BaseModel where V : IView { private delegate void VoidDelegate(); public Func ModelValueGetter { get; private set; } public Action ModelValueSetter { get; private set; } public Func ViewValueGetter { get; private set; } public Action ViewValueSetter { get; private set; } public ModelViewPropagationItem(Func modelValueGetter, Action viewValueSetter) : this(modelValueGetter, null, null, viewValueSetter) { } public ModelViewPropagationItem(Action modelValueSetter, Func viewValueGetter) : this(null, modelValueSetter, viewValueGetter, null) { } public ModelViewPropagationItem(Func modelValueGetter, Action modelValueSetter, Func viewValueGetter, Action 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)); } } 

这允许您创建此对象的实例,然后使用“SyncToModel”和“SyncToView”来回移动值。 下面的内容允许您对这些内容进行分组,并通过一次调用来回移动数据:

 public class ModelViewPropagationGroup : List> where M : BaseModel where V : IView { public ModelViewPropagationGroup(params IModelViewPropagationItem[] 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)); } } 

用法看起来像这样:

 private static readonly ModelViewPropagationItem UsernamePI = new ModelViewPropagationItem(m => m.Username.Value, (m, x) => m.Username.Value = x, v => v.Username, (v, x) => v.Username = x); private static readonly ModelViewPropagationItem PasswordPI = new ModelViewPropagationItem(m => m.Password.Value, (m, x) => m.Password.Value = x, v => v.Password, (v, x) => v.Password = x); private static readonly ModelViewPropagationGroup GeneralPG = new ModelViewPropagationGroup(UsernamePI, PasswordPI); public UserPrincipal Login_Click() { GeneralPG.SyncAllToModel(this.Model, this.View); return this.Model.DoLogin(); } 

希望这可以帮助!

var pr = typeof(CCategory).GetProperties()。选择(i => i.Name).ToList(); ;

宣言:

  class Foo { public string Bar(Expression> 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; } } 

用法:

  var foo = new Foo(); var propName = foo.Bar(d=>d.DummyProperty) Console.WriteLine(propName); //write "DummyProperty" string in shell