如何让我的通用方法起作用?

时间:2012-12-31 01:56:58

标签: c# generics lambda expression extension-methods

我正在为我的WPF viewModel基类编写一个小扩展,它允许我以比在getter中引发多个PropertyChanged事件的标准方法更清晰的方式定义属性之间的依赖关系:

所以而不是:

public int ThisProperty
{  
    get
    {
        thisProperty = value;
        RaisePropertyChangedEvent("ThisProperty");
        RaisePropertyChangedEvent("FirstDependentProperty");
        RaisePropertyChangedEvent("SecondDependentProperty");
    }
}

我希望能够在我的ViewModels构造函数中执行类似的操作:

RegisterDependencies("This Property", 
    "FirstDependentProperty", "SecondDependentProperty");

我在viewModel中定义了以下方法(删除了错误检查以减少代码量):

public void RegisterDependencies(string property, params string[] dependencies)
{
    foreach (string item in dependencies)
    {
        IList<string> deps;
        if (dependenciesList.TryGetValue(item, out deps))
        {
            if (!deps.Contains(property))
            {
                deps.Add(property);
            }
        }
        else
        {
            deps = new List<string>();
            deps.Add(property);
            dependenciesList[item] = deps;
        }
    }
}

我的viewmodel使用以下方法订阅PropertyChanged事件:

void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    IList<string> dependencies;
    if (dependenciesList.TryGetValue(e.PropertyName, out dependencies))
    {
        foreach (string item in dependencies)
        {
            RaisePropertyChangedEvent(item);
        }
    }
}

这只是检查属性是否具有任何依赖关系,如果是,则也会为这些事件引发PropertyChanged事件。这一切都很美妙,到目前为止一直很好。

我真正想要的是使用lambda表达式而不是字符串,以便我获得自动完成和更好的重新分解支持。我所追求的是这样的:

RegisterDependencies<ViewModelType>(p => p.Property, 
    p => p.FirstDependency,
    p => p.SecondDependency); //and so on...

我重新定义了方法签名,如下所示:

public void RegisterDependencies<T>(Expression<Func<T, object>> property, 
    prams Expression<Func<T, object>>[] dependencies)
    {
    }

目前,该方法只是尝试将表达式转换为字符串,这就是我要解开的地方。我不知道如何从表达式中获取属性的名称并将其转换为字符串。

Josh Smith的MVVM Foundation包含一个名为PropertyChangedObserver的类,它有一段代码可以实现这一点,我试图适应我的例子:

private static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
    var lambda = expression as LambdaExpression;

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

    return memberExpression != null ? ((PropertyInfo)memberExpression.Member).Name : null;
}

这是在我更新的RegisterDependencies方法中调用的(这实际上是该方法的整个代码):

string propertyName = GetPropertyName(property);

foreach (Expression<Func<T, object>> expr in dependencies)
{
    string dependencyName = GetPropertyName(expr);
}

运行此操作时会产生XamlParseException。这很难调试,因为它没有抛出标准异常窗口。 Visual Studio的输出窗口提供了更多的信息,似乎最初的异常是InvalidCastException,虽然我不确定为什么两个表达式的类型相同。

任何人都可以解决这个问题吗?

3 个答案:

答案 0 :(得分:1)

跟进Matti Virkkunen的评论,Member属性返回一个可以表示属性或字段的MemberInfo对象。如果这是你的问题,不用担心; Name属性实际上是MemberInfo类的成员,因此无需强制转换为PropertyInfo。

如果还有其他事情发生,我们需要更多信息来提供帮助;你能用你正在传递的实际lambda表达式发布呼叫网站吗?

答案 1 :(得分:1)

似乎问题在于将表达式传递给RegisterDependencies

以下(稍加适应的代码)成功运行。

    public static void Test()
    {
        var deps = RegisterDependencies((KeyValuePair<string,string> p) => p.Key, (KeyValuePair<string,string> p) => p.Value);
        foreach(var d in deps)
        {
            Console.WriteLine(d); // Prints Key, Then Value
        }
    }
    public static IEnumerable<string> RegisterDependencies<T>(Expression<Func<T, object>> property,    params Expression<Func<T, object>>[] dependencies)
    {
        var deps = new List<string>();
        deps.Add(GetPropertyName(property));
        foreach (var d in dependencies)
        {
            deps.Add(GetPropertyName(d));
        }
        return deps;
    }

    public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
    {
        var lambda = expression as LambdaExpression;

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

        return memberExpression != null ? ((PropertyInfo)memberExpression.Member).Name : null;
    }

答案 2 :(得分:0)

事实证明,不知何故,我设法输入错误的属性名称,例如我想要的地方:

p => p.Quantity

我实际输入了:

p => p.quantity

注意小写q 。由于我在构造函数中调用它,我也可以访问所有私有成员(因此我的类型有一个名为quantity的字段)这就是为什么这不会导致编译器抱怨而是{{1在运行时生成,因为它显然不是属性。

@Phoog&amp; Tilak - 谢谢你的帮助。你的两个答案都帮助我解决了这个问题,这就是为什么我对你的两个答案进行了投票,尽管这是“正确的”答案。