通过财产并获得课程

时间:2016-07-14 19:11:15

标签: c# reflection properties

这可能是一个微不足道的问题,但我在这里画了一个空白,似乎无法在网上找到答案。

基本上,我正在尝试创建一个方法,该方法将属于INotifyPropertyChanged类的2个属性作为参数(要在反射中使用的实际属性,而不是属性值),并将它们保持在“同步“就像一个绑定。

实施例

我有一个名为Student的类,其中包含一个名为int SemesterScore的属性。我有另一个名为Semester的类,其中包含一个名为int Score的属性。这两个类都实现了IPropertyNotifyChanged

现在,让我们暂时假设我们不能扩展任何类(如在我的现实场景中),并且我可能在不同的类中有多次我想要使用它。

基本上,我希望能够在我的一个类中调用一个方法,将两个属性“链接”在一起。也就是说,如果其中一个更改,它将自动更新另一个。

在非工作代码中,这是基本概念:

public class Student : INotifyPropertyChanged
{
    private int _semesterScore;
    public  int SemeseterScore
    {
        get { return _semesterScore; }
        set { [ set property stuff with property changed] }
    }
}

public class Semester: INotifyPropertyChanged
{
    private int _score;
    public  int Score
    {
        get { return _score; }
        set { [ set property stuff with property changed] }
    }
}

public class Entry
{
    public static void Main(string[] args)
    {
        Student student = new Student();
        Semester semester = new Semester();

        AttachProperties(student.SemesterScore, semester.Score); // This obviously won't work, but this is where I pass the properties in

        semester.Score = 7;
        Console.WriteLine(student.SemesterScore); // Output will be 7
    }

    public static void AttachProperties([sometype] prop1, [sometype] prop2)
    {
        // Sudo code
        prop1.classInstance.PropertyChanged += (pe)
        {
            if (pe.Property == prop1.Name)
                prop2.Value = prop1.Value;
        }

        prop2.classInstance.PropertyChanged += (pe)
        {
            if (pe.Property == prop2.Name)
                prop1.Value = prop2.Value;
        }
    }
}

有没有办法做到这一点?我知道一些变通方法(也就是通过INotifyPropertyChanged类和属性名称,然后进行一些反思以使其工作),但是在我的编码中出现了几次传递属性实例(并用它做的东西)的问题生涯。

5 个答案:

答案 0 :(得分:3)

执行此操作的一种方法是使用Observable,例如上面建议的@ itay-podhacer。

但是如果你想只使用Reflection实现,那么INotifyPropertyChanged就是你可以做到的。

首先,让SemesterScoreStudent同时实施INotifyPropertyChanged

public class Student : INotifyPropertyChanged
{
    private int semesterScore;

    public int SemesterScore
    {
        get { return semesterScore; }
        set
        {
            semesterScore = value; 
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Semester : INotifyPropertyChanged
{
    private int score;

    public int Score
    {
        get { return score; }
        set
        {
            score = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

现在,让我们在AttachProperties辅助方法中将属性绑定在一起。为了做到这一点,我们将使AttachProperties方法采用Expression<Func<T,object>参数,以便我们避免传递魔术字符串并可以使用Reflection来检索属性名称。

顺便说一下,要在生产中运行它,你可能想要记住反射代码的性能。

private static void AttachProperties<T1,T2>(Expression<Func<T1, object>> property1, T1 instance1, Expression<Func<T2, object>> property2,  T2 instance2)
            where T1 : INotifyPropertyChanged
            where T2 : INotifyPropertyChanged
{
    var p1 = property1.GetPropertyInfo();
    var p2 = property2.GetPropertyInfo();

        //A NULL or empty PropertyName in PropertyChangeEventArgs means that all properties changed
        //See: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged(v=vs.110).aspx#Anchor_1

        ((INotifyPropertyChanged)instance1).PropertyChanged += (_, e) =>
        {
            if (e.PropertyName == p1.Name || string.IsNullOrEmpty(e.PropertyName))
            {
                SyncProperties(p1, p2, instance1, instance2);
            }
        };

        ((INotifyPropertyChanged)instance2).PropertyChanged += (_, e) =>
        {
            if (e.PropertyName == p2.Name || string.IsNullOrEmpty(e.PropertyName))
            {
                SyncProperties(p2, p1, instance2, instance1);
            }
        };
}

private static void SyncProperties(PropertyInfo sourceProperty, PropertyInfo targetProperty, object sourceInstance, object targetInstance)
{
    var sourceValue = sourceProperty.GetValue(sourceInstance);
    var targetValue = targetProperty.GetValue(targetInstance);

    if (!sourceValue.Equals(targetValue))
    {
        targetProperty.SetValue(targetInstance, sourceValue);
    }
}

最后,这里是从代码中检索PropertyInfo的Reflection代码:

public static class ReflectionExtension
{
    public static PropertyInfo GetPropertyInfo<T>(this Expression<Func<T, object>> expression)
    {
        var memberExpression = GetMemberExpression(expression);
        return (PropertyInfo)memberExpression.Member;
    }

    private static MemberExpression GetMemberExpression<TModel, T>(Expression<Func<TModel, T>> expression)
    {
        MemberExpression memberExpression = null;
        if (expression.Body.NodeType == ExpressionType.Convert)
        {
            var body = (UnaryExpression)expression.Body;
            memberExpression = body.Operand as MemberExpression;
        }
        else if (expression.Body.NodeType == ExpressionType.MemberAccess)
        {
            memberExpression = expression.Body as MemberExpression;
        }

        if (memberExpression == null)
        {
            throw new ArgumentException("Not a member access", "expression");
        }

        return memberExpression;
    }
}

完成所有这些后,您现在可以保持两个属性同步:

public class PropertySyncTests
{
    public void Should_sync_properties()
    {
        var semester = new Semester();
        var student = new Student();
        AttachProperties(x => x.Score, semester, x => x.SemesterScore, student);
        semester.Score = 7;
        student.SemesterScore.ShouldBe(7);
    }
}

答案 1 :(得分:2)

  

我知道一些变通方法(也就是传递INotifyPropertyChanged类和属性名称,然后做一些反思才能使它工作),但传递属性实例(并用它做的东西)的问题已经出现了几次在我的编码生涯中。

这最终是实现目标的方式。但是,我认为你可能不知道的一个关键技巧是Expression Trees。可以创建一个以Expression<Func<T>>作为参数的函数,然后深入研究表达式树以发现INotifyPropertyChanged实例和参数中给出的属性。用法可能如下所示:

    AttachProperties(() => student.SemesterScore, () => semester.Score);

以上示例中AttachProperties的参数为Expression,结构如下。

<LambdaExpression>            () => student.SemesterScore
  Body <MemberExpression>           student.SemesterScore
    Member <PropertyInfo>                   SemesterScore
    Expression <MemberExpression>   student
      Member <FieldInfo>            [closure class.]student
      Expression <ConstantExpression> [closure]
        Value                       [closure instance]

请注意,您正在使用lambda表达式中的student创建闭包,因此要获取student的值,您需要使用反射来获取{{1}的值1}}字段。获取[closure class].student属性只是正确地转换表达式并从传入的lambda表达式中获取SemesterScore属性。

答案 2 :(得分:1)

订阅学期的PropertyChanged事件:

Student student = new Student();
Semester semester = new Semester();
semester.PropertyChanged += Semester_PropertyChanged;

然后将新分数分配给学生

private void Semester_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    student.SemesterScore = semester.Score;
}

这会触发学生的PropertyChanged事件,如果分数发生变化,也会更新他的SemesterScore

答案 3 :(得分:1)

看看Reactive UI Extensions,它会让你有能力观察&#34;更改属性,然后在发生此类更改后更新(或执行任何操作)。

它允许你做这样的事情:

student
    .WhenAnyValue(item => item.SemeseterScore)
    .Subscribe(item => 
    {
        semester.Score = item.SemeseterScore
    });

semester
    .WhenAnyValue(item => item.Score)
    .Subscribe(item => 
    {
        item.SemeseterScore = semester.Score
    });

您可能需要在班级中添加Ignore标志,并在Subscribe代码中打开和关闭它,这样您就不会在两个班级之间创建无限循环的更新

答案 4 :(得分:1)

好的,所以我将@StriplingWarrior和@Pedro的答案结合起来得到我的最终结果:

    public static void AttachProperties<T1, T2>(Expression<Func<T1>> property1, Expression<Func<T2>> property2)
    {
        var instance1 = Expression.Lambda<Func<object>>(((MemberExpression)property1.Body).Expression).Compile()();
        var iNotify1 = instance1 as INotifyPropertyChanged;
        var prop1 = GetPropertyInfo(property1);

        var instance2 = Expression.Lambda<Func<object>>(((MemberExpression)property2.Body).Expression).Compile()();
        var iNotify2 = instance2 as INotifyPropertyChanged;
        var prop2 = GetPropertyInfo(property2);

        AttachProperty(prop1, iNotify1, prop2, iNotify2);
        AttachProperty(prop2, iNotify2, prop1, iNotify1);
    }

    static void AttachProperty(
        PropertyInfo property1,
        INotifyPropertyChanged class1Instance,
        PropertyInfo property2,
        INotifyPropertyChanged class2Instance)
    {
        class2Instance.PropertyChanged += (_, propArgs) =>
        {
            if (propArgs.PropertyName == property2.Name || string.IsNullOrEmpty(propArgs.PropertyName))
            {
                var prop = property2.GetValue(class2Instance);
                property1.SetValue(class1Instance, prop);
            }
        };
    }

    static PropertyInfo GetPropertyInfo<T1>(Expression<Func<T1>> property)
    {
        MemberExpression expression = null;
        if (property.Body.NodeType == ExpressionType.Convert)
        {
            var body = (UnaryExpression)property.Body;
            expression = body.Operand as MemberExpression;
        }
        else if (property.Body.NodeType == ExpressionType.MemberAccess)
        {
            expression = property.Body as MemberExpression;
        }

        if (expression == null)
        {
            throw new ArgumentException("Not a member access", nameof(property));
        }

        return expression.Member as PropertyInfo;
    }

这在我给出的示例和现实生活中的项目中都能正常工作。谢谢!