微软应该为INotifyPropertyChanged实现一些时髦的东西,就像在自动属性中,只需要指定{get;设置;通知;} 我认为这样做很有意义。或者做这个手术有什么并发症吗?

我们能在属性中实现类似notify的东西吗。在你的类中实现INotifyPropertyChanged是否有一个优雅的解决方案,或者唯一的方法是在每个属性中引发PropertyChanged事件。

如果不是,我们可以写一些东西来自动生成一段代码来引发PropertyChanged事件?


当前回答

我实际上还没有机会尝试这个自己,但下次我设置一个大需求的INotifyPropertyChanged项目,我打算写一个Postsharp属性,将在编译时注入代码。喜欢的东西:

[NotifiesChange]
public string FirstName { get; set; }

将成为:

private string _firstName;

public string FirstName
{
   get { return _firstname; }
   set
   {
      if (_firstname != value)
      {
          _firstname = value;
          OnPropertyChanged("FirstName")
      }
   }
}

我不确定这在实践中是否有效,我需要坐下来尝试一下,但我不明白为什么不。我可能需要让它接受一些参数的情况下,超过一个OnPropertyChanged需要被触发(如果,例如,我有一个FullName属性在上面的类)

目前我在Resharper使用一个自定义模板,但即使这样,我也厌倦了我所有的属性太长。


啊,一个快速的谷歌搜索(我应该在写这篇文章之前做的)显示至少有一个人在这里之前做过类似的事情。跟我想的不完全一样,但也足以证明这个理论是正确的。

其他回答

我真的很喜欢Marc的解决方案,但我认为可以稍微改进一下,以避免使用“魔法字符串”(不支持重构)。与其将属性名作为字符串使用,不如将其创建为lambda表达式:

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

只需要在Marc的代码中添加以下方法,就可以了:

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    if (selectorExpression == null)
        throw new ArgumentNullException("selectorExpression");
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null)
        throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}

protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

顺便说一句,这是受到这篇博客文章的启发。

是的,更好的办法当然存在。 下面就是:

循序渐进的教程由我缩减,基于这篇有用的文章。

创建新项目 将城堡核心包安装到项目中

安装包的城堡。核心

只安装mvvm light库

安装包MvvmLightLibs

在项目中添加两个类:

NotifierInterceptor

public class NotifierInterceptor : IInterceptor
    {
        private PropertyChangedEventHandler handler;
        public static Dictionary<String, PropertyChangedEventArgs> _cache =
          new Dictionary<string, PropertyChangedEventArgs>();

        public void Intercept(IInvocation invocation)
        {
            switch (invocation.Method.Name)
            {
                case "add_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                case "remove_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                default:
                    if (invocation.Method.Name.StartsWith("set_"))
                    {
                        invocation.Proceed();
                        if (handler != null)
                        {
                            var arg = retrievePropertyChangedArg(invocation.Method.Name);
                            handler(invocation.Proxy, arg);
                        }
                    }
                    else invocation.Proceed();
                    break;
            }
        }

        private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
        {
            PropertyChangedEventArgs arg = null;
            _cache.TryGetValue(methodName, out arg);
            if (arg == null)
            {
                arg = new PropertyChangedEventArgs(methodName.Substring(4));
                _cache.Add(methodName, arg);
            }
            return arg;
        }
    }

ProxyCreator

public class ProxyCreator
{
    public static T MakeINotifyPropertyChanged<T>() where T : class, new()
    {
        var proxyGen = new ProxyGenerator();
        var proxy = proxyGen.CreateClassProxy(
          typeof(T),
          new[] { typeof(INotifyPropertyChanged) },
          ProxyGenerationOptions.Default,
          new NotifierInterceptor()
          );
        return proxy as T;
    }
}

创建你的视图模型,例如:

-

 public class MainViewModel
    {
        public virtual string MainTextBox { get; set; }

        public RelayCommand TestActionCommand
        {
            get { return new RelayCommand(TestAction); }
        }

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }

将绑定放入xaml: <TextBox Text="{绑定MainTextBox}" ></TextBox> . <Button Command="{Binding TestActionCommand}" >Test</Button> . 在代码隐藏文件MainWindow.xaml.cs中放入如下代码行:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

享受。

注意! !所有有界属性都应该用 关键字virtual,因为它们被城堡代理用于重写。

我刚刚发现ActiveSharp - Automatic INotifyPropertyChanged,我还没有使用它,但它看起来不错。

从它的网站上引用…


发送属性更改通知 没有指定属性名为 字符串。

相反,可以这样写属性:

public int Foo
{
    get { return _foo; }
    set { SetValue(ref _foo, value); }  // <-- no property name here
}

注意,不需要将属性的名称作为字符串包含。ActiveSharp可靠而正确地为自己计算出来。它的工作基于这样一个事实,即你的属性实现通过ref传递支持字段(_foo) (ActiveSharp使用“by ref”调用来识别哪个支持字段被传递,并从字段标识属性)。

所有这些答案都很好。

我的解决方案是使用代码片段来完成这项工作。

这使用了对PropertyChanged事件最简单的调用。

保存此代码段并像使用“fullprop”代码段一样使用它。

位置可以在'工具\代码片段管理器…的菜单。

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>inotifypropfull</Title>
            <Shortcut>inotifypropfull</Shortcut>
            <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
            <Description>Code snippet for property and backing field with notification</Description>
            <Author>Ofir Zeitoun</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set { 
            $field$ = value;
            var temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs("$property$"));
            }
        }
    }
    $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

您可以根据需要修改调用(使用上述解决方案)

这就是大规模的过度工程。这比仅仅以正确的方式进行要复杂得多,而且几乎没有任何好处。如果你的IDE支持代码片段(Visual Studio/MonoDevelop支持),那么你可以让这个实现变得非常简单。你需要输入的只是属性的类型和属性名。额外的三行代码将自动生成。