Implementando INotifyPropertyChanged - existe uma maneira melhor?

647

A Microsoft deveria ter implementado algo rápido para INotifyPropertyChanged , como nas propriedades automáticas, basta especificar {get; set; notify;} que acho que faz muito sentido fazê-lo. Ou existem complicações para fazer isso?

Nós mesmos podemos implementar algo como 'notificar' em nossas propriedades. Existe uma solução elegante para implementar INotifyPropertyChangedem sua classe ou a única maneira de fazer isso é aumentando o PropertyChangedevento em cada propriedade.

Caso contrário, podemos escrever algo para gerar automaticamente o trecho de código para gerar o PropertyChanged evento?

PK
fonte
7
code.google.com/p/notifypropertyweaver pode ser útil
Ian Ringrose
7
o link acima está morto. github.com/SimonCropp/NotifyPropertyWeaver
01/18
2
Você poderia usar DependencyObject e DependencyProperties. HA! Eu fiz uma graça.
28414 Phil
5
Na época, não era possível fazer alterações no C #, pois tínhamos um enorme registro de interdependências. Então, quando MVVM nasceu, eu acho, nós realmente não nos esforçamos muito para resolver esse problema e eu sei que a equipe de Padrões e Práticas teve algumas tentativas ao longo do caminho (portanto, você também recebeu o MEF como parte disso). pesquisa). Hoje eu acho que [CallerMemberName] é a resposta para o acima.
Scott Barnes

Respostas:

633

Sem usar algo como postsharp, a versão mínima que eu uso usa algo como:

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

Cada propriedade é então algo como:

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

o que não é enorme; também pode ser usado como classe base, se você desejar. O boolretorno de SetFieldinforma se foi um no-op, caso você queira aplicar outra lógica.


ou ainda mais fácil com o C # 5:

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

que pode ser chamado assim:

set { SetField(ref name, value); }

com o qual o compilador adicionará o "Name"automaticamente.


O C # 6.0 facilita a implementação:

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

... e agora com o 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);
}
Marc Gravell
fonte
4
Bom truque Marc! Sugeri uma melhoria para usar uma expressão lambda em vez do nome da propriedade, veja a minha resposta
Thomas Levesque
7
@ Thomas - o lambda é muito bom, mas acrescenta muita sobrecarga para algo que é realmente muito simples. Um truque útil, mas não tenho certeza se é sempre prático.
Marc Gravell
14
@ Marc - Sim, pode desempenho provavelmente degrade ... No entanto, eu realmente gosto do fato de que é verificado em tempo de compilação, e está corretamente reformulado pelo comando "Renomear"
Thomas Levesque
4
@Gusdor felizmente, com C # 5 não há nenhuma necessidade de compromisso - você pode obter o melhor de ambos via (como notas Pedro77)[CallerMemberName]
Marc Gravell
4
@Gusdor a linguagem e o framework são separados; você pode usar o compilador C # 5, o destino .NET 4 e apenas adicionar o atributo ausente - ele funcionará bem. Ele só precisa ter o nome correto e estar no espaço para nome correto. Não precisa estar em uma montagem específica.
Marc Gravell
196

A partir do .Net 4.5, finalmente existe uma maneira fácil de fazer isso.

O .NET 4.5 apresenta um novo Atributo de Informação do Chamador.

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

Provavelmente, é uma boa ideia adicionar um comparador à função também.

EqualityComparer<T>.Default.Equals

Mais exemplos aqui e aqui

Consulte também Informações sobre chamadas (C # e Visual Basic)

Daniel Little
fonte
12
Brilhante! Mas por que é genérico?
abatishchev
@abatishchev Eu acho que não precisa ser, eu estava apenas brincando com a ideia de ter a função definir a propriedade também. Vou ver se consigo atualizar minha resposta para fornecer a solução completa. Os exemplos extras fazem um bom trabalho nesse meio tempo.
Daniel Little
3
Foi introduzido pelo C # 5.0. Não tem nada a ver com o .net 4.5, mas esta é uma ótima solução!
J. Lennon
5
@J. O Lennon .net 4.5 ainda tem algo a ver com isso, afinal o atributo vem de algum lugar msdn.microsoft.com/en-au/library/…
Daniel Little
@Lavinski alterar seu aplicativo para por exemplo, NET 3.5 e ver o que vai funcionar (em vs2012)
J. Lennon
162

Eu realmente gosto da solução de Marc, mas acho que ela pode ser um pouco melhorada para evitar o uso de uma "string mágica" (que não suporta refatoração). Em vez de usar o nome da propriedade como uma sequência, é fácil torná-la uma expressão lambda:

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

Basta adicionar os seguintes métodos ao código de Marc, ele fará o truque:

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

Aliás, isso foi inspirado neste URL atualizado da postagem do blog

Thomas Levesque
fonte
6
Há pelo menos uma estrutura usando esse método, ReactiveUI .
AlSki
Muito tarde, isso significou passar pela reflexão, o que significou um impacto no desempenho. Pode ser aceitável, mas definir uma propriedade não é um lugar onde eu gostaria que meu aplicativo passasse por muitos ciclos.
Bruno Brant
1
@BrunoBrant Tem certeza de que houve um impacto no desempenho? De acordo com o post do blog, a reflexão acontece durante o tempo de compilação, em vez do tempo de execução (ou seja, reflexão estática).
Nathaniel Elkins
6
Acredito que todo o OnPropertyChanged <T> esteja obsoleto com o nome do operador de C # 6, tornando esse monstro um pouco mais elegante.
Traubenfuchs
5
@Traubenfuchs, na verdade, C # 5 do atributo CallerMemberName torna ainda mais simples, desde que você não precisa passar alguma coisa ...
Thomas Levesque
120

Há também Fody, que tem um suplemento PropertyChanged , que permite escrever isso:

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

... e no momento da compilação injeta notificações alteradas de propriedade.

Tom Gilder
fonte
7
Eu acho que isso é exatamente o OP estava procurando quando eles pediram "Nós mesmos podemos implementar algo como 'avisar' em nossas propriedades Existe uma solução elegante para implementar INotifyPropertyChanged em sua classe."
Ashoat
3
Essa é a única solução elegante e funciona perfeitamente como o @CADbloke disse. E eu também era cético em relação ao tecelão, mas verifiquei / verifiquei novamente o código de IL e é perfeito, é simples, faz tudo o que você precisa e nada mais. Ele também conecta e chama o nome do método que você designou na classe base, se o NotifyOnProp ..., o OnNotify ... não importa, por isso funciona bem com qualquer classe base que você possa ter e implementa o INotify. #
9789 NSGaga-inativo principalmente
1
Você pode facilmente verificar o que o tecelão está fazendo, dar uma olhada na janela de saída da compilação, listar todas as coisas que mudou em PropertyChanged. O uso da extensão VScolorOutput com o padrão regex "Fody/.*?:",LogCustom2,Truedestaca-a na cor "2 personalizadas". Eu o fiz rosa brilhante, para que seja fácil de encontrar. Apenas monte tudo, é a maneira mais legal de fazer qualquer coisa que possua muitas digitações repetitivas.
CAD cara
@mahmoudnezarsarhan não, não é, lembro-me de que houve uma pequena alteração na forma como deve ser configurada, mas o Fody PropertyChanged ainda está ativo e ativo.
Larry
65

Eu acho que as pessoas deveriam prestar um pouco mais de atenção ao desempenho; ele realmente afeta a interface do usuário quando há muitos objetos a serem vinculados (pense em uma grade com mais de 10.000 linhas) ou se o valor do objeto muda frequentemente (aplicativo de monitoramento em tempo real).

Peguei várias implementações encontradas aqui e em outros lugares e fiz uma comparação; confira a comparação de desempenho das implementações INotifyPropertyChanged .


Aqui está uma espiada no resultado Implementação vs Tempo de Execução

Peijen
fonte
14
-1: não há sobrecarga de desempenho: CallerMemberName são alterados para valores literais no tempo de compilação. Apenas tente descompilar seu aplicativo.
JYL
aqui está a seguinte pergunta e resposta: stackoverflow.com/questions/22580623/…
uli78
1
@JYL, você está certo de que CallerMemberName não adicionou uma sobrecarga grande. Eu devo ter implementado algo errado da última vez que tentei. Vou atualizar o blog e responder para refletir o benchmark da implementação de CallerMemberName e Fody posteriormente.
Peijen
1
Se você tem uma grade de 10.000 na interface do usuário, então você provavelmente deve ser combinando abordagens para o desempenho punho, como paginação, onde você só mostrar 10, 50, 100, 250 batidas por página ...
Austin Rhymer
Austin Rhymer, se você tiver grandes dados + 50, use a virtualização de dados, não há necessidade de carregar todos os dados; eles carregarão apenas os dados visíveis na área exibida na seção de rastreamento atual!
Bilal
38

Apresento uma classe Bindable no meu blog em http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ O Bindable usa um dicionário como um pacote de propriedades. É fácil o suficiente adicionar as sobrecargas necessárias para uma subclasse gerenciar seu próprio campo de suporte usando parâmetros ref.

  • Nenhuma corda mágica
  • Sem reflexão
  • Pode ser aprimorado para suprimir a pesquisa de dicionário padrão

O código:

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

Pode ser usado assim:

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}
TiMoch
fonte
2
Esta é uma boa solução, mas a única desvantagem é que há um pequeno impacto no desempenho envolvendo boxe / unboxing.
MCattle
1
Gostaria de sugerir a usar protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)e também verificar if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))em Set (para aumentar e economize quando primeiro conjunto para o valor padrão)
Miquel
1
O @Miquel adicionando suporte a valores padrão personalizados pode ser útil, com certeza, no entanto, você deve tomar cuidado para aumentar apenas o evento alterado quando o valor realmente for alterado. Definir uma propriedade com o mesmo valor que tinha não deve gerar eventos. Devo admitir que, na maioria dos casos, é inofensivo, no entanto, já estive algumas vezes com propriedades sendo configuradas milhares de vezes com o mesmo valor com eventos que destroem a capacidade de resposta da interface do usuário.
TiMoch 01/03
1
@stakx Tenho algumas aplicações que construir este para apoiar o padrão lembrança para desfazer / refazer ou para permitir que a unidade de padrão de trabalho em aplicações onde nhibernate não é utilizável
TiMoch
1
Eu realmente gosto dessa solução em particular: notação curta, nada de proxy dinâmico, sem interferência de IL, etc. Embora você possa reduzi-lo , removendo a necessidade de especificar T toda vez que Get for tornando Get return dinâmico. Eu sei, isso afeta o desempenho do tempo de execução, mas agora o código para getters e setters pode finalmente ser sempre o mesmo e, em uma linha , louve ao Senhor! PS: você deve tomar cuidado adicional dentro do método Get (uma vez ao escrever a classe base) ao retornar valores dinâmicos para tipos de valor como dinâmicos. Certifique-se de sempre retornar valores padrão corretos (isso pode ser feito)
evilkos
15

Na verdade, ainda não tive a chance de tentar isso, mas da próxima vez que estiver configurando um projeto com um grande requisito para INotifyPropertyChanged, pretendo escrever um atributo Postsharp que injete o código no momento da compilação. Algo como:

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

Se tornará:

private string _firstName;

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

Não tenho certeza se isso funcionará na prática e preciso sentar e experimentar, mas não vejo por que não. Talvez seja necessário fazê-lo aceitar alguns parâmetros para situações em que mais de um OnPropertyChanged precisa ser acionado (se, por exemplo, eu tivesse uma propriedade FullName na classe acima)

Atualmente, estou usando um modelo personalizado no Resharper, mas mesmo com isso estou ficando cansado de todas as minhas propriedades serem tão longas.


Ah, uma rápida pesquisa no Google (o que eu deveria ter feito antes de escrever isso) mostra que pelo menos uma pessoa fez algo assim antes aqui . Não é exatamente o que eu tinha em mente, mas perto o suficiente para mostrar que a teoria é boa.

Martin Harris
fonte
6
Uma ferramenta gratuita chamada Fody parece fazer a mesma coisa, funcionando como um injetor de código genérico em tempo de compilação. Pode ser baixado no Nuget, assim como os pacotes de plug-in PropertyChanged e PropertyChanging.
Triynko
11

Sim, certamente existe uma maneira melhor. Aqui está:

O tutorial passo a passo encolheu por mim, com base neste artigo útil .

  • Criar novo projeto
  • Instale o pacote principal do castelo no projeto

Pacote de instalação Castle.Core

  • Instale apenas bibliotecas mvvm light

MvvmLightLibs do pacote de instalação

  • Adicione duas classes no projeto:

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;
    }
}
  • Crie seu modelo de visualização, por exemplo:

-

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

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

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • Coloque ligações no xaml:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • Coloque a linha de código no arquivo code-behind MainWindow.xaml.cs assim:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Aproveitar.

insira a descrição da imagem aqui

Atenção!!! Todas as propriedades delimitadas devem ser decoradas com a palavra-chave virtual porque elas foram usadas pelo proxy do castelo para substituir.

testCoder
fonte
Estou interessado em saber qual versão do Castle você está usando. Eu estou usando 3.3.0 eo método CreateClassProxy não tem esses parâmetros: type, interfaces to apply, interceptors.
iAbstract
Deixa pra lá, eu estava usando o CreateClassProxy<T>método genérico . Muito diferente ... hmmm, querendo saber por que tão limitado com o método genérico. :(
IAbstract 13/04/16
7

Uma abordagem muito semelhante à AOP é injetar o material INotifyPropertyChanged em um objeto já instanciado em tempo real. Você pode fazer isso com algo como o Castle DynamicProxy. Aqui está um artigo que explica a técnica:

Adicionando INotifyPropertyChanged a um objeto existente

HokieMike
fonte
5

Olhe aqui : http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

Está escrito em alemão, mas você pode baixar o ViewModelBase.cs. Todos os comentários no arquivo cs são escritos em inglês.

Com esta classe ViewModelBase, é possível implementar propriedades vinculáveis ​​semelhantes às conhecidas Propriedades de Dependência:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}
DotNetMastermind
fonte
1
Link quebrado.
Guge
4

Com base na resposta de Thomas, que foi adaptada de uma resposta de Marc, transformei o código alterado da propriedade refletida em uma classe base:

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

O uso é o mesmo que a resposta de Thomas, exceto que você pode passar propriedades adicionais a serem notificadas. Isso foi necessário para lidar com colunas calculadas que precisam ser atualizadas em uma grade.

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

Eu tenho isso dirigindo uma coleção de itens armazenados em um BindingList exposto por meio de um DataGridView. Isso eliminou a necessidade de fazer chamadas manuais de Refresh () para a grade.

StuffOfInterest
fonte
4

Deixe-me apresentar minha própria abordagem chamada Yappi . Pertence aos geradores de classe derivados do proxy Runtime, adicionando nova funcionalidade a um objeto ou tipo existente, como o Dynamic Proxy do Caste Project.

Ele permite implementar INotifyPropertyChanged uma vez na classe base e depois declarar classes derivadas no seguinte estilo, ainda suportando INotifyPropertyChanged para novas propriedades:

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

A complexidade da construção de classe ou proxy derivada pode estar oculta atrás da seguinte linha:

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

E todo o trabalho de implementação INotifyPropertyChanged pode ser feito assim:

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

É totalmente seguro para refatoração, não usa reflexão após a construção do tipo e é rápido o suficiente.

Kelqualyn
fonte
Por que você precisa TDeclarationdigitar o parâmetro PropertyImplementation? Certamente você pode encontrar o tipo apropriado para chamar (não callvirt) o getter / setter apenas com TImplementation?
Andrew Savinykh
A implementação de TI funciona na maioria dos casos. As exceções são: 1. Propriedades redefinidas com o "novo" keyvord C #. 2. Propriedades da implementação explícita da interface.
28415 Kelelalyn
3

Todas essas respostas são muito legais.

Minha solução é usar os trechos de código para fazer o trabalho.

Isso usa a chamada mais simples para o evento PropertyChanged.

Salve esse snippet e use-o como usa o snippet 'fullprop'.

o local pode ser encontrado no menu 'Ferramentas \ Gerenciador de trechos de código ...' no 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>

Você pode modificar a chamada como quiser (para usar as soluções acima)

Ofir
fonte
2

Se você estiver usando dinâmica no .NET 4.5, não precisará se preocupar INotifyPropertyChanged.

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

se Name estiver associado a algum controle, ele funcionará bem.

Dilshod
fonte
1
alguma desvantagem de usar isso?
juFo 15/09/17
2

Outra solução combinada está usando o 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));
        }
    }
}

Uso:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}
Ofir
fonte
2
Isso é rápido? O acesso ao quadro da pilha não está vinculado a algum requisito de permissão? Isso é robusto em um contexto de uso de assíncrono / espera?
Stéphane Gourichon
@ StéphaneGourichon Não, não é. Acessar o quadro da pilha significa um impacto considerável no desempenho na maioria dos casos.
Bruno Brant
Sim, existe, você pode vê-lo em codereview.stackexchange.com/questions/13823/...
Ofir
Observe que inlining pode ocultar o get_Foométodo no modo Release.
precisa
2

Criei um método de extensão na minha biblioteca base para reutilização:

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

Isso funciona com o .Net 4.5 por causa do CallerMemberNameAttribute . Se você quiser usá-lo com uma versão .Net anterior, precisará alterar a declaração do método de:...,[CallerMemberName] string propertyName = "", ... para...,string propertyName, ...

Uso:

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

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

Eu resolvi dessa maneira (é um pouco trabalhoso, mas certamente é o mais rápido em tempo de execução).

No VB (desculpe, mas acho que não é difícil traduzi-lo em C #), faço essa substituição com o RE:

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

com:

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

Este transofrm todo o código como este:

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

No

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

E se eu quiser ter um código mais legível, posso ser o oposto fazendo a seguinte substituição:

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

Com

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

Eu lancei para substituir o código IL do método set, mas não posso escrever muito código compilado em IL ... Se um dia eu o escrever, eu direi!

Lucio Menci
fonte
2

Eu mantenho isso por aí como um trecho. O C # 6 adiciona uma sintaxe interessante para chamar o manipulador.

// 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));
    }
}
Mike Ward
fonte
2

Aqui está uma versão Unity3D ou não CallerMemberName do NotifyPropertyChanged

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
}

Esse código permite que você escreva campos de apoio de propriedades como este:

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

Além disso, no re-afiador, se você criar um snippet de padrão / pesquisa, também poderá automatizar seu fluxo de trabalho, convertendo campos de objetos simples no suporte acima.

Padrão de Pesquisa:

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

Substituir Padrão:

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

Eu escrevi um artigo que ajuda com isso ( https://msdn.microsoft.com/magazine/mt736453 ). Você pode usar o pacote SolSoft.DataBinding NuGet. Então você pode escrever um código como este:

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

Benefícios:

  1. classe base é opcional
  2. nenhuma reflexão sobre cada 'valor definido'
  3. podem ter propriedades que dependem de outras propriedades e todas geram automaticamente os eventos apropriados (o artigo tem um exemplo disso)
Mark Sowul
fonte
2

Embora obviamente haja muitas maneiras de fazer isso, com exceção das respostas mágicas da AOP, nenhuma das respostas parece definir a propriedade de um modelo diretamente do modelo de exibição sem ter um campo local para referência.

O problema é que você não pode fazer referência a uma propriedade. No entanto, você pode usar uma Ação para definir essa propriedade.

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

Isso pode ser usado como a seguinte extração de código.

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

Confira este repositório do BitBucket para obter uma implementação completa do método e algumas maneiras diferentes de obter o mesmo resultado, incluindo um método que usa LINQ e um método que usa reflexão. Observe que esses métodos têm desempenho mais lento.

Dan
fonte
1

Outras coisas que você pode considerar ao implementar esses tipos de propriedades é o fato de que o INotifyPropertyChang * ed * ing usa classes de argumento de evento.

Se você tiver um grande número de propriedades que estão sendo definidas, o número de instâncias da classe de argumentos de eventos pode ser enorme, considere armazená-las em cache, pois são uma das áreas em que uma explosão de cadeia pode ocorrer.

Dê uma olhada nesta implementação e explique por que ela foi concebida.

Josh Smiths Blog

Pedro
fonte
1

Acabei de encontrar o ActiveSharp - Automatic INotifyPropertyChanged , ainda não o usei, mas parece bom.

Para citar seu site ...


Envie notificações de alteração de propriedade sem especificar o nome da propriedade como uma sequência.

Em vez disso, escreva propriedades como esta:

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

Observe que não há necessidade de incluir o nome da propriedade como uma sequência. O ActiveSharp calcula isso de maneira confiável e correta. Funciona com base no fato de que sua implementação de propriedade passa o campo de apoio (_foo) por ref. (O ActiveSharp usa essa chamada "por ref" para identificar qual campo de apoio foi passado e, a partir do campo, identifica a propriedade).

Ian Ringrose
fonte
1

Uma ideia usando reflexão:

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
Jack
fonte
Isso é muito legal, eu gosto mais do que abordagem de expressão. No lado negativo, deve ser mais lento.
Nawfal 12/08/14
1

Sei que essa pergunta já tem um zilhão de respostas, mas nenhuma delas parecia certa para mim. Meu problema é que não quero resultados de desempenho e estou disposto a tolerar um pouco de verbosidade apenas por esse motivo. Também não me importo muito com propriedades automáticas, o que me levou à seguinte solução:

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

Em outras palavras, a solução acima é conveniente se você não se importa em fazer isso:

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

Prós

  • Sem reflexão
  • Notifica apenas se o valor antigo! = Novo valor
  • Notificar várias propriedades de uma só vez

Contras

  • Sem propriedades automáticas (você pode adicionar suporte para as duas!)
  • Alguma verbosidade
  • Boxe (pequeno desempenho atingido?)

Infelizmente, ainda é melhor do que fazer isso,

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

Para cada propriedade, o que se torna um pesadelo com a verbosidade adicional ;-(

Observe que não afirmo que esta solução seja melhor em termos de desempenho em comparação com as outras, apenas que é uma solução viável para quem não gosta das outras soluções apresentadas.

James M
fonte
1

Eu vim com essa classe base para implementar o padrão observável, praticamente faz o que você precisa ( "automaticamente" implementando o conjunto e obtendo). Passei uma hora e uma hora nisso como protótipo, para que ele não tenha muitos testes de unidade, mas prova o conceito. Observe que ele usa o Dictionary<string, ObservablePropertyContext>para remover a necessidade de campos particulares.

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

Aqui está o uso

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); }
    }
  }
Homero Barbosa
fonte
1

Sugiro usar ReactiveProperty. Este é o método mais curto, exceto Fody.

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

em vez de

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

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

( DOCS )

soi
fonte
0

Outra ideia...

 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));
    }
}
Toro
fonte
0

=> aqui minha solução com os seguintes recursos

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. sem reflexão
  2. notação curta
  3. nenhuma sequência mágica no seu código comercial
  4. Reutilização de PropertyChangedEventArgs no aplicativo
  5. Possibilidade de notificar várias propriedades em uma declaração
Bruno
fonte
0

Usa isto

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

}

Dude505
fonte