实现INotifyPropertyChanged-是否存在更好的方法?


647

微软应该为它实现一些快速的功能INotifyPropertyChanged,例如在自动属性中,只需指定{get; set; notify;} 我认为这样做是很有意义的。还是有任何并发​​症要做?

我们自己可以在属性中实现“通知”之类的功能吗?是否有一个优雅的解决方案可以INotifyPropertyChanged在您的类中实现,还是唯一的解决方法是PropertyChanged在每个属性中引发事件。

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




2
您可以改用DependencyObject和DependencyProperties。哈!我很有趣。
2014年


5
当时我们无法进行C#更改,因为我们之间存在大量的相互依存关系。所以我想当MVVM诞生时,我们实际上并没有投入太多精力来解决这个问题,而且我知道Patterns&Practices团队在此过程中做了一些努力(因此,您也获得了MEF的支持)研究线程)。今天,我认为[CallerMemberName]是上述解决方案。
Scott Barnes

Answers:


633

在不使用postsharp之类的情况下,我使用的最低版本使用的是:

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

那么每个属性都类似于:

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

这不是很大;如果需要,它也可以用作基类。在bool从回SetField告诉你,如果它是一个空操作,如果你想申请其他逻辑。


甚至更容易使用C#5:

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

可以这样称呼:

set { SetField(ref name, value); }

编译器将使用它"Name"自动添加。


C#6.0使实现更容易:

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

...现在使用C#7:

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

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

4
马克妙招!我建议使用lambda表达式而不是属性名称进行改进,请参阅我的答案
Thomas Levesque,2009年

7
@Thomas-lambda很好,但实际上却很简单的事情却增加了很多开销。一个方便的技巧,但是我不确定它是否总是实用的。
Marc Gravell

14
@Marc-是的,它可能会降低性能...但是,我非常喜欢这样的事实,即它在编译时已检查,并且已通过“重命名”命令正确地进行了重构
Thomas Levesque

4
@Gusdor幸运的是,C#5,没有必要妥协-您可以通过获得两全其美(如Pedro77笔记)[CallerMemberName]
马克·Gravell

4
@Gusdor语言和框架是分开的;您可以使用C#5编译器,目标.NET 4并自己添加缺少的属性 -它将正常工作。它只需要具有正确的名称并在正确的名称空间中即可。它不需要在特定的程序集中。
马克·格雷夫

196

从.Net 4.5开始,终于有了一种简单的方法。

.Net 4.5引入了新的呼叫者信息属性。

private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
     // make sure only to call this if the value actually changes

     var handler = PropertyChanged;
     if (handler != null) {
        handler(this, new PropertyChangedEventArgs(caller));
     }
}

最好在函数中添加一个比较器。

EqualityComparer<T>.Default.Equals

这里这里有更多示例

另请参阅呼叫者信息(C#和Visual Basic)


12
辉煌!但是为什么它是通用的?
abatishchev 2012年

@abatishchev我猜不是必须的,我只是想让函数也设置属性。我将看看是否可以更新我的答案以提供完整的解决方案。在此期间,额外的示例做得很好。
丹尼尔·

3
它是由C#5.0引入的。它与.net 4.5无关,但这是一个很好的解决方案!
J. Lennon

5
@J。列侬.NET 4.5仍然有一些东西,用它做的,毕竟属性来自某处msdn.microsoft.com/en-au/library/...
丹尼尔·利特尔

@Lavinski将您的应用程序更改为例如.NET 3.5,然后看看会起作用(在vs2012中)
J. Lennon

162

我真的很喜欢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;
}

顺便说一句,这是受此博客文章 更新URL的启发


6
至少有一个使用此方法的框架ReactiveUI
AlSki 2011年

很晚了,这意味着要进行反思,这意味着性能受到打击。可以接受,但是设置属性并不是我希望我的应用程序花费很多时间的地方。
布鲁诺·布兰特

1
@BrunoBrant您确定性能受到打击吗?根据博客文章,反射发生在编译时而不是运行时(即静态反射)。
纳撒尼尔·埃尔金斯

6
我相信您整个OnPropertyChanged <T>都已被C#6的运算符nameof淘汰,这使这个怪物变得更时尚了。
Traubenfuchs 2015年

5
@Traubenfuchs,实际上,C#5的CallerMemberName属性使它更加简单,因为您根本不需要传递任何东西……
Thomas Levesque

120

还有Fody,它具有PropertyChanged加载项,可让您编写以下代码:

[ImplementPropertyChanged]
public class Person 
{        
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
}

...并在编译时注入属性更改的通知。


7
我认为这正是OP在他们问“我们可以自己在属性中实现类似'notify'的东西吗?在您的类中实现INotifyPropertyChanged的优雅解决方案”时
寻找的吗

3
这确实是唯一的优雅解决方案,并且可以像@CADbloke所说的那样完美地工作。我也对编织者表示怀疑,但是我检查/重新检查了后面的IL代码,它很完美,很简单,可以满足您的所有需求,而且别无其他。它还会钩住并调用您在基类中为其指定的任何方法名称,无论NotifyOnProp ...,OnNotify ...都无关紧要,因此可以与您可能拥有并实现INotify的任何基类一起很好地工作。 。
NSGaga,大多是不活动

1
您可以轻松地仔细检查编织者的工作,查看构建输出窗口,其中列出了编织的所有PropertyChanged内容。将VScolorOutput扩展名与正则表达式一起使用时,将其"Fody/.*?:",LogCustom2,True突出显示为“自定义2”颜色。我把它做成鲜艳的粉红色,所以很容易找到。只是Fody一切,这是做任何具有大量重复键入操作的最简洁的方法。
CAD bloke

@mahmoudnezarsarhan不,不是,我记得它的配置方式稍有变化,但Fody PropertyChanged仍然有效。
拉里

65

我认为人们应该更多地关注性能。当要绑定的对象很多时(例如具有10,000+行的网格),或者对象的值频繁更改(实时监控应用程序),它确实会影响UI。

我采用了在这里和其他地方找到的各种实现,并进行了比较。检查一下INotifyPropertyChanged实现的性能比较


看一下结果 实施与运行时


14
-1:没有性能开销:在编译时将CallerMemberName更改为文字值。只需尝试反编译您的应用即可。
JYL 2014年

这里是根据问题和答案:stackoverflow.com/questions/22580623/...
uli78

1
@JYL,您是正确的,CallerMemberName并没有增加很大的开销。上次尝试时,我一定犯了一些错误。稍后,我将更新博客并回答以反映CallerMemberName和Fody实现的基准。
Peijen 2014年

1
如果用户界面中的网格超过10,000,则您可能应该结合使用多种方法来处理性能,例如分页,每页仅显示
10、50、100、250

奥斯汀·莱茵(Austin Rhymer),如果您有大数据+ 50个使用数据虚拟化的功能,则无需加载所有数据,它将仅加载在当前依序显示区域中可见的数据!
Bilal

38

我在http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/的博客中介绍了Bindable类/ Bindable使用字典作为属性包。为子类添加必要的重载很容易,以便使用ref参数管理其自己的后备字段。

  • 没有魔力弦
  • 无反省
  • 可以改进以抑制默认字典查找

编码:

public class Bindable : INotifyPropertyChanged {
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    /// <summary>
    /// Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    protected T Get<T>([CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T)value;
        return default(T);
    }

    /// <summary>
    /// Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <remarks>Use this overload when implicitly naming the property</remarks>
    protected void Set<T>(T value, [CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(name)))
            return;
        _properties[name] = value;
        OnPropertyChanged(name);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

可以这样使用:

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}

2
这是一个不错的解决方案,但是唯一的缺点是装箱/拆箱对性能的影响很小。
MCattle

1
我建议使用protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)并签if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))入Set(在首次设置为默认值时引发并保存)
Miquel 2015年

1
@Miquel添加对自定义默认值的支持肯定会很有用,但是您应该注意仅在实际更改值时才引发更改的事件。将属性设置为其具有的相同值不应引发事件。我必须承认在大多数情况下它是无害的,但是我已经经历了很多次,因为事件破坏了UI响应性,因此将属性设置成相同的值数千次。
TiMoch'3

1
@stakx我有一些基于此的应用程序来支持撤消/重做的记忆模式,或者在无法使用nhibernate的应用程序中启用工作单元模式
TiMoch 2015年

1
我真的很喜欢这个特殊的解决方案:短符号,没有动态代理,没有IL绑定等。尽管如此,您可以通过使Get返回为动态来消除每次为Get指定T的需要,从而使其更短。我知道,这会影响运行时性能,但是现在,用于getter和setter的代码最终可以总是相同的,并且在一行中,赞美主!PS:当返回值类型的默认值作为动态值时,您应该在Get方法内部(写基类一次)时要格外小心。确保始终返回正确的默认值(可以这样做)
evilkos

15

我实际上还没有机会自己尝试一下,但是下一次我要建立一个对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中使用自定义模板,但是即使那样,我也厌倦了我所有的属性太长了。


嗯,快速的Google搜索(我在写这篇文章之前就应该这样做)表明,至少有一个人在这里之前做了类似的事情。不完全是我的初衷,但足够接近以证明该理论是好的。


6
一个名为Fody的免费工具似乎起着同样的作用,可以充当通用的编译时代码注入器。它可以在Nuget中下载,其PropertyChanged和PropertyChanging插件程序包也可以下载。
Triynko 2014年

11

是的,确实存在更好的方法。这里是:

基于这篇有用的文章,循序渐进的教程使我精疲力尽。

  • 建立新专案
  • 将城堡核心软件包安装到项目中

安装包Castle.Core

  • 仅安装mvvm light库

安装包MvvmLightLibs

  • 在项目中添加两个类:

通知程序拦截器

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="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • 将如下代码行放在代码隐藏文件MainWindow.xaml.cs中:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • 请享用。

在此处输入图片说明

注意!!!所有有界属性都应使用关键字virtual装饰,因为城堡代理将其用于覆盖。


我想知道您使用的是哪个版本的Castle。我使用的3.3.0和CreateClassProxy方法没有这些参数:typeinterfaces to applyinterceptors
IAbstract 2016年

没关系,我使用的是通用CreateClassProxy<T>方法。... hmmm有很大的不同,想知道为什么用通用方法如此有限。:(
IAbstract 2016年


5

在这里查看:http : //dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

它是用德语编写的,但是您可以下载ViewModelBase.cs。cs文件中的所有注释均以英语编写。

使用此ViewModelBase-Class,可以实现类似于众所周知的Dependency Properties的可绑定属性:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}

1
链接断开。
古格

4

基于托马斯(Thomas)的答案,该答案改编自马克(Marc)的答案,我将反映属性更改的代码转换为基类:

public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) 
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var me = selectorExpression.Body as MemberExpression;

        // Nullable properties can be nested inside of a convert function
        if (me == null)
        {
            var ue = selectorExpression.Body as UnaryExpression;
            if (ue != null)
                me = ue.Operand as MemberExpression;
        }

        if (me == null)
            throw new ArgumentException("The body must be a member expression");

        OnPropertyChanged(me.Member.Name);
    }

    protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(selectorExpression);
        foreach (var item in additonal)
            OnPropertyChanged(item);
    }
}

用法与Thomas的答案相同,不同之处在于您可以传递其他属性以进行通知。这对于处理需要在网格中刷新的计算列很有必要。

private int _quantity;
private int _price;

public int Quantity 
{ 
    get { return _quantity; } 
    set { SetField(ref _quantity, value, () => Quantity, () => Total); } 
}
public int Price 
{ 
    get { return _price; } 
    set { SetField(ref _price, value, () => Price, () => Total); } 
}
public int Total { get { return _price * _quantity; } }

我用它来驱动存储在通过DataGridView公开的BindingList中的项目的集合。它消除了我对网格进行手动Refresh()调用的需要。


4

让我介绍一下我自己的称为Yappi的方法。它属于运行时代理生成的类生成器,它向现有对象或类型添加了新功能,例如Caste Project的动态代理。

它允许在基类中实现一次INotifyPropertyChanged,然后以以下样式声明派生类,仍然为新属性支持INotifyPropertyChanged:

public class Animal:Concept
{
    protected Animal(){}
    public virtual string Name { get; set; }
    public virtual int Age { get; set; }
}

派生类或代理构造的复杂性可以隐藏在以下行后面:

var animal = Concept.Create<Animal>.New();

并且所有INotifyPropertyChanged实现工作都可以像这样完成:

public class Concept:INotifyPropertyChanged
{
    //Hide constructor
    protected Concept(){}

    public static class Create<TConcept> where TConcept:Concept
    {
        //Construct derived Type calling PropertyProxy.ConstructType
        public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
        //Create constructing delegate calling Constructor.Compile
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        var caller = PropertyChanged;
        if(caller!=null)
        {
            caller(this, eventArgs);
        }
    }

    //define implementation
    public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
    {
        public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
        }
        /// <summary>
        /// Overriding property setter implementation.
        /// </summary>
        /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
        /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
        /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
        /// <typeparam name="TResult">Type of property.</typeparam>
        /// <param name="property">PropertyInfo of property.</param>
        /// <returns>Delegate, corresponding to property setter implementation.</returns>
        public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            //This code called once for each declared property on derived type's initialization.
            //EventArgs instance is shared between all events for each concrete property.
            var eventArgs = new PropertyChangedEventArgs(property.Name);
            //get delegates for base calls.
            Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
            Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);

            var comparer = EqualityComparer<TResult>.Default;

            return (pthis, value) =>
            {//This code executes each time property setter is called.
                if (comparer.Equals(value, getter(pthis))) return;
                //base. call
                setter(pthis, value);
                //Directly accessing Concept's protected method.
                pthis.OnPropertyChanged(eventArgs);
            };
        }
    }
}

它对于重构是完全安全的,在类型构造之后不使用反射并且足够快。


为什么需要TDeclarationon的type参数PropertyImplementation?当然,您可以找到合适的类型来仅使用TImplementation
Andrew Savinykh 2015年

在大多数情况下,实施都是可行的。例外是:1.用“新” C#keyvord重新定义的属性。2.显式接口实现的属性。
Kelqualyn

3

所有这些答案都很好。

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

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

保存此代码段,并在使用“ fullprop”代码段时使用。

该位置可以在Visual Studio的“工具\代码段管理器...”菜单中找到。

<?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>

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


2

如果您在.NET 4.5中使用动态功能,则无需担心INotifyPropertyChanged

dynamic obj = new ExpandoObject();
obj.Name = "John";

如果Name绑定到某个控件,则可以正常工作。


1
使用这个有什么缺点吗?
2010年

2

另一个组合解决方案是使用StackFrame:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(ref T field, T value)
    {
        MethodBase method = new StackFrame(1).GetMethod();
        field = value;
        Raise(method.Name.Substring(4));
    }

    protected void Raise(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

用法:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}

2
这样快吗?对堆栈框架的访问是否不符合某些权限要求?在使用async / await的上下文中,它是否健壮?
斯特凡纳·古里科

@StéphaneGourichon不,不是。在大多数情况下,访问堆栈框架意味着相当大的性能损失。
布鲁诺·布兰特


请注意,内联可能会get_Foo在“发布”模式下隐藏该方法。
bytecode77

2

我在基础库中创建了一个扩展方法以供重用:

public static class INotifyPropertyChangedExtensions
{
    public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
               PropertyChangedEventHandler handler, ref T field, T value, 
               [CallerMemberName] string propertyName = "",
               EqualityComparer<T> equalityComparer = null)
    {
        bool rtn = false;
        var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
        if (!eqComp.Equals(field,value))
        {
            field = value;
            rtn = true;
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(propertyName);
                handler(sender, args);
            }
        }
        return rtn;
    }
}

由于CallerMemberNameAttribute,因此可与.Net 4.5 一起使用。如果要与早期的.Net版本一起使用,则必须将方法声明从:更改...,[CallerMemberName] string propertyName = "", ......,string propertyName, ...

用法:

public class Dog : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
        }
    }
}

2

我以这种方式解决(这有点费力,但是运行时肯定更快)。

在VB中(很抱歉,但是我认为在C#中翻译它并不难),我用RE进行了替换:

(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)

与:

Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n

此transofrm所有代码如下:

<Bindable(True)>
Protected Friend Property StartDate As DateTime?

Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
    Get
        Return _StartDate
    End Get
    Set(Value As DateTime?)
        If _StartDate <> Value Then
            _StartDate = Value
            RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
        End If
    End Set
End Property

如果我想拥有一个更具可读性的代码,则可以做以下替换:

Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property

${Attr} ${Def} ${Name} As ${Type}

我扔掉替换set方法的IL代码,但是我不能在IL中编写很多编译后的代码...如果有一天我写它,我会说你!


2

我将其作为摘要。C#6为调用处理程序添加了一些不错的语法。

// INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(property, value) == false)
    {
        property = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2

这是NotifyPropertyChanged的Unity3D或非CallerMemberName版本

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> property)
    {
        var expression = (MemberExpression) property.Body;
        var propertyName = expression.Member.Name;

        Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
        return propertyName;
    }

    #region Notification Handlers

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

此代码使您可以编写如下的属性支持字段:

  public string Text
    {
        get { return Get<string>(); }
        set { Set(value); }
    }

此外,在reshaper中,如果您创建模式/搜索代码段,则还可以通过将简单的prop字段转换为上述支持来使您的工作流程自动化。

搜索模式:

public $type$ $fname$ { get; set; }

替换图案:

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}

2

我写了一篇文章对此有所帮助(https://msdn.microsoft.com/magazine/mt736453)。您可以使用SolSoft.DataBinding NuGet包。然后,您可以编写如下代码:

public class TestViewModel : IRaisePropertyChanged
{
  public TestViewModel()
  {
    this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
  }

  private readonly NotifyProperty<string> m_nameProperty;
  public string Name
  {
    get
    {
      return m_nameProperty.Value;
    }
    set
    {
      m_nameProperty.SetValue(value);
    }
  }

  // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}

优点:

  1. 基类是可选的
  2. 没有对每个“设定值”的反思
  3. 可以具有依赖于其他属性的属性,并且它们都会自动引发适当的事件(本文提供了一个示例)

2

尽管有很多种方法可以做到这一点,但AOP魔术答案除外,似乎没有答案可以直接从视图模型中设置模型的属性,而无需引用本地字段。

问题是您无法引用属性。但是,您可以使用操作来设置该属性。

protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
    {
        return false;
    }

    property(newValue);
    RaisePropertyChanged(propertyName);
    return true;
}

可以像下面的代码提取一样使用。

public int Prop {
    get => model.Prop;
    set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}

查看此BitBucket存储库,以获取该方法的完整实现以及获得相同结果的几种不同方法,包括使用LINQ的方法和使用反射的方法。请注意,这些方法会降低性能。


1

实现这些类型的属性时,您可能还需要考虑的其他事实是,INotifyPropertyChang都使用事件参数类。

如果要设置的属性很多,那么事件参数类实例的数量可能会很多,您应该考虑对其进行缓存,因为它们是可能发生字符串爆炸的区域之一。

请看一下此实现并解释其构想的原因。

Josh Smiths博客


1

我刚刚发现ActiveSharp-自动INotifyPropertyChanged,我还没有使用过,但是看起来不错。

要引用它的网站...


发送属性更改通知,而无需将属性名称指定为字符串。

相反,编写如下属性:

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

请注意,无需将属性名称包含为字符串。ActiveSharp可靠并正确地自行解决了这一问题。它基于您的属性实现通过ref传递后备字段(_foo)的事实而工作。(ActiveSharp使用“ by ref”调用来标识传递了哪个后备字段,并从该字段中标识属性)。


1

使用反射的想法:

class ViewModelBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {

        // Get Name of Property
        string name = mb.Name.Substring(4);

        // Detect Change
        bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);

        // Return if no change
        if (!changed) return false;

        // Update value
        oldValue = newValue;

        // Raise Event
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }//if

        // Notify caller of change
        return true;

    }//method

    string name;

    public string Name {
        get { return name; }
        set {
            Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
        }
    }//method

}//class

这非常酷,我更喜欢表达方式。不利的一面是,它应该变慢。
nawfal 2014年

1

我意识到这个问题已经有了数不胜数的答案,但是没有一个人觉得我很合适。我的问题是,我不希望任何表现受到影响,仅出于这个原因,我愿意忍受一些冗长的细节。我也不太在乎自动属性,这导致我采用以下解决方案:

public abstract class AbstractObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
    {
        //Set value if the new value is different from the old
        if (!Source.Equals(NewValue))
        {
            Source = NewValue;

            //Notify all applicable properties
            foreach (var i in Notify)
                OnPropertyChanged(i);

            return true;
        }

        return false;
    }

    public AbstractObject()
    {
    }
}

换句话说,如果您不介意这样做,上述解决方案将很方便:

public class SomeObject : AbstractObject
{
    public string AnotherProperty
    {
        get
        {
            return someProperty ? "Car" : "Plane";
        }
    }

    bool someProperty = false;
    public bool SomeProperty
    {
        get
        {
            return someProperty;
        }
        set
        {
            SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
        }
    }

    public SomeObject() : base()
    {
    }
}

优点

  • 无反省
  • 仅在旧值!=新值时通知
  • 一次通知多个属性

缺点

  • 没有自动属性(不过,您可以同时添加对两者的支持!)
  • 一些冗长
  • 拳击(对性能影响不大?)

las,还是比这样做好,

set
{
    if (!someProperty.Equals(value))
    {
        someProperty = value;
        OnPropertyChanged("SomeProperty");
        OnPropertyChanged("AnotherProperty");
    }
}

对于每个属性,这都将成为附加冗长的噩梦;-(

请注意,我并不认为该解决方案在性能上比其他解决方案更好,只是对于那些不喜欢其他解决方案的人来说,这是一个可行的解决方案。


1

我想出了这个基类来实现可观察的模式,几乎可以完成您所需要的(“自动”实现set和get)。我花了一个小时的时间作为原型,所以它没有太多的单元测试,但是证明了这个概念。请注意,它使用Dictionary<string, ObservablePropertyContext>来消除对私有字段的需要。

  public class ObservableByTracking<T> : IObservable<T>
  {
    private readonly Dictionary<string, ObservablePropertyContext> _expando;
    private bool _isDirty;

    public ObservableByTracking()
    {
      _expando = new Dictionary<string, ObservablePropertyContext>();

      var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
      foreach (var property in properties)
      {
        var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType)
        {
          Value = GetDefault(property.PropertyType)
        };

        _expando[BuildKey(valueContext)] = valueContext;
      }
    }

    protected void SetValue<T>(Expression<Func<T>> expression, T value)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var originalValue = (T)_expando[key].Value;
      if (EqualityComparer<T>.Default.Equals(originalValue, value))
      {
        return;
      }

      _expando[key].Value = value;
      _isDirty = true;
    }

    protected T GetValue<T>(Expression<Func<T>> expression)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var value = _expando[key].Value;
      return (T)value;
    }

    private KeyContext GetKeyContext<T>(Expression<Func<T>> expression)
    {
      var castedExpression = expression.Body as MemberExpression;
      if (castedExpression == null)
      {
        throw new Exception($"Invalid expression.");
      }

      var parameterName = castedExpression.Member.Name;

      var propertyInfo = castedExpression.Member as PropertyInfo;
      if (propertyInfo == null)
      {
        throw new Exception($"Invalid expression.");
      }

      return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName};
    }

    private static string BuildKey(ObservablePropertyContext observablePropertyContext)
    {
      return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}";
    }

    private static string BuildKey(string parameterName, Type type)
    {
      return $"{type.Name}.{parameterName}";
    }

    private static object GetDefault(Type type)
    {
      if (type.IsValueType)
      {
        return Activator.CreateInstance(type);
      }
      return null;
    }

    public bool IsDirty()
    {
      return _isDirty;
    }

    public void SetPristine()
    {
      _isDirty = false;
    }

    private class KeyContext
    {
      public string PropertyName { get; set; }
      public Type PropertyType { get; set; }
    }
  }

  public interface IObservable<T>
  {
    bool IsDirty();
    void SetPristine();
  }

这是用法

public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
  {
    public ObservableByTrackingTestClass()
    {
      StringList = new List<string>();
      StringIList = new List<string>();
      NestedCollection = new List<ObservableByTrackingTestClass>();
    }

    public IEnumerable<string> StringList
    {
      get { return GetValue(() => StringList); }
      set { SetValue(() => StringIList, value); }
    }

    public IList<string> StringIList
    {
      get { return GetValue(() => StringIList); }
      set { SetValue(() => StringIList, value); }
    }

    public int IntProperty
    {
      get { return GetValue(() => IntProperty); }
      set { SetValue(() => IntProperty, value); }
    }

    public ObservableByTrackingTestClass NestedChild
    {
      get { return GetValue(() => NestedChild); }
      set { SetValue(() => NestedChild, value); }
    }

    public IList<ObservableByTrackingTestClass> NestedCollection
    {
      get { return GetValue(() => NestedCollection); }
      set { SetValue(() => NestedCollection, value); }
    }

    public string StringProperty
    {
      get { return GetValue(() => StringProperty); }
      set { SetValue(() => StringProperty, value); }
    }
  }

1

我建议使用ReactiveProperty。这是除Fody外最短的方法。

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    ...
    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

代替

public class Data
{
    // Don't need boiler-plate and INotifyPropertyChanged

    // props
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
}

DOCS


0

另一个主意...

 public class ViewModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
    protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
        _propertyStore[propertyName] = value;
        OnPropertyChanged(propertyName);
    }
    protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        object ret;
        if (_propertyStore.TryGetValue(propertyName, out ret))
        {
            return (T)ret;
        }
        else
        {
            return default(T);
        }
    }

    //Usage
    //public string SomeProperty {
    //    get { return GetValue<string>();  }
    //    set { SetValue(value); }
    //}

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

0

=> 这里我的解决方案具有以下功能

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. 没有反省
  2. 简写
  3. 您的业​​务代码中没有魔术字符串
  4. PropertyChangedEventArgs在整个应用程序中的可重用性
  5. 可以在一条语句中通知多个属性

0

用这个

using System;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;


public static class ObservableFactory
{
    public static T Create<T>(T target)
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Target should be an interface", "target");

        var proxy = new Observable<T>(target);
        return (T)proxy.GetTransparentProxy();
    }
}

internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
{
    private readonly T target;

    internal Observable(T target)
        : base(ImplementINotify(typeof(T)))
    {
        this.target = target;
    }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;

        if (methodCall != null)
        {
            return HandleMethodCall(methodCall);
        }

        return null;
    }

    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;



    IMessage HandleMethodCall(IMethodCallMessage methodCall)
    {
        var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
        var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;

        if (isPropertySetterCall)
        {
            OnPropertyChanging(propertyName);
        }

        try
        {
            object methodCalltarget = target;

            if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
                methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
            {
                methodCalltarget = this;
            }

            var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);

            if (isPropertySetterCall)
            {
                OnPropertyChanged(methodCall.MethodName.Substring(4));
            }

            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (TargetInvocationException invocationException)
        {
            var exception = invocationException.InnerException;
            return new ReturnMessage(exception, methodCall);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanging(string propertyName)
    {
        var handler = PropertyChanging;
        if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
    }

    public static Type ImplementINotify(Type objectType)
    {
        var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());

        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);

        var moduleBuilder = dynamicAssembly.DefineDynamicModule(
            tempAssemblyName.Name,
            tempAssemblyName + ".dll");

        var typeBuilder = moduleBuilder.DefineType(
            objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);

        typeBuilder.AddInterfaceImplementation(objectType);
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
        var newType = typeBuilder.CreateType();
        return newType;
    }
}

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.