[英]Linq expressions and extension methods to get property name
我正在看這篇文章,它描述了在POCO屬性之間進行數據綁定的簡單方法: 數據綁定POCO屬性
Bevan的評論之一包括一個簡單的Binder類,可用於完成此類數據綁定。 它對我需要的東西很有用,但我想實現Bevan為改進課程所做的一些建議,即:
此外,鑒於按字符串指定屬性容易出錯,您可以使用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<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;
}
用法:
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);
}
}
用法:
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);
}
}
這個問題非常類似於: 從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
。 在我的測試中,性能似乎相當,所以如果這是一個壞主意,請糾正我。
/// <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
(檢查分隔符可能甚至是過度殺傷)
var pr = typeof(CCategory).GetProperties()。選擇(i => i.Name).ToList(); ;
宣言:
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;
}
}
用法:
var foo = new Foo<DummyType>();
var propName = foo.Bar(d=>d.DummyProperty)
Console.WriteLine(propName); //write "DummyProperty" string in shell
這可能不僅僅是你要求的,但我已經做了類似的事情來處理兩個對象之間的屬性映射:
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));
}
}
這允許您創建此對象的實例,然后使用“SyncToModel”和“SyncToView”來回移動值。 下面的內容允許您對這些內容進行分組,並通過一次調用來回移動數據:
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));
}
}
用法看起來像這樣:
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();
}
希望這可以幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.