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

发布于 2024-10-21 06:24:18 字数 1915 浏览 9 评论 0原文

我正在看这篇文章,它描述了一种在 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);
                }
            };
    }
}

I was looking at this post that describes a simple way to do databinding between POCO properties: Data Binding POCO Properties

One of the comments by Bevan included a simple Binder class that can be used to accomplish such data binding. It works great for what I need but I would like to implement some of the suggestions that Bevan made to improve the class, namely:

  • Checking that source and target are
    assigned
  • Checking that the properties
    identified by sourcePropertyName and
    targetPropertyName exist
  • Checking for type compatibility
    between the two properties

Also, given that specifying properties by string is error prone, you could use Linq expressions and extension methods instead. Then instead of writing

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

you could write

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

I'm pretty sure I can handle the first three (though feel free to include those changes) but I have no clue how to use Linq expressions and extension methods to be able to write code without using property name strings.

Any tips?

Here is the original code as found in the link:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

季末如歌 2024-10-28 06:24:18

以下代码将从 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);
    }    
}

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);
    }    
}
漫漫岁月 2024-10-28 06:24:18

这个问题非常类似于:Retriving Property name from lambda expression

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

我不知道不知道是否需要绑定到“子属性”,但是检查 lambda.Body 中的 Member.Name 只会返回“最终”属性,而不是“完全” -合格的”财产。

例如) 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

(检查分隔符甚至可能有点过分)

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)

溇涏 2024-10-28 06:24:18

这可能超出或不完全符合您的要求,但我已经做了类似处理两个对象之间属性映射的操作:

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

希望这有帮助!

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!

娇纵 2024-10-28 06:24:18

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

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

鸵鸟症 2024-10-28 06:24:18

声明:

    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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文