Pergunta

Eu acho que essa é a questão, de qualquer maneira. Eu estou usando um RelayCommand, que decora um ICommand com dois delegados. Um deles é predicado para o _canExecute eo outro é de Acção para o método _execute.

--- motivação do fundo -

A motivação tem a ver com ViewModels de teste de unidade para um WPF apresentação. Um padrão comum é que eu tenho um ViewModel que tem uma ObservableCollection, e eu quero um teste de unidade para provar os dados em que a coleta é o que eu esperar dado alguns dados de origem (que também precisa ser convertido em uma coleção de ViewModels). Mesmo que os dados em ambas as coleções parece o mesmo no depurador, parece que o teste falhar devido a uma falha igualdade no RelayCommand do ViewModel. Aqui está um exemplo do teste de unidade com falha:

[Test]
    public void Creation_ProjectActivities_MatchFacade()
    {
        var all = (from activity in _facade.ProjectActivities
                   orderby activity.BusinessId
                   select new ActivityViewModel(activity, _facade.SubjectTimeSheet)).ToList();

        var models = new ObservableCollection<ActivityViewModel>(all);
        CollectionAssert.AreEqual(_vm.ProjectActivities, models);
    }

--- Voltar para delegar a igualdade ----

Aqui está o código para o RelayCommand - é basicamente um rip-off direto da ideia de Josh Smith, com uma implementação de igualdade que eu adicionei em uma tentativa de resolver esse problema:

public class RelayCommand : ICommand, IRelayCommand
{
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    /// <summary>Creates a new command that can always execute.</summary>
    public RelayCommand(Action<object> execute) : this(execute, null) { }

    /// <summary>Creates a new command which executes depending on the logic in the passed predicate.</summary>
    public RelayCommand(Action<object> execute, Predicate<object> canExecute) {
        Check.RequireNotNull<Predicate<object>>(execute, "execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    [DebuggerStepThrough]
    public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter) { _execute(parameter); }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof(RelayCommand)) return false;
        return Equals((RelayCommand)obj);
    }

    public bool Equals(RelayCommand other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other._execute, _execute) && Equals(other._canExecute, _canExecute);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((_execute != null ? _execute.GetHashCode() : 0) * 397) ^ (_canExecute != null ? _canExecute.GetHashCode() : 0);
        }
    }

}

Em um teste de unidade onde eu definir efetivamente o delegado _execute o mesmo método (_canExecute é nula em ambos os casos), o teste de unidade falhar nesta linha:

return Equals(other._execute, _execute) && Equals(other._canExecute, _canExecute)

saída do depurador:

?_execute
{Method = {Void <get_CloseCommand>b__0(System.Object)}}
base {System.MulticastDelegate}: {Method = {Void CloseCommand>b__0(System.Object)}}

?other._execute
{Method = {Void <get_CloseCommand>b__0(System.Object)}} 
base {System.MulticastDelegate}: {Method = {Void CloseCommand>b__0(System.Object)}}

Alguém pode explicar o que estou ausente e que a correção é?

---- EDITADO OBSERVAÇÕES ----

Como Mehrdad apontou, o get_CloseCommand da sessão de depuração parece um pouco estranho no começo. É realmente apenas um get propriedade, mas levanta o ponto de por que a igualdade do delegado é problemático se eu preciso fazer truques para fazê-lo funcionar.

Alguns dos ponto de MVVM é expor tudo o que pode ser útil em uma apresentação como propriedades, assim você pode usar ligação WPF. A classe particular eu estava testando tem um WorkspaceViewModel nele de hierarquia, que é apenas um ViewModel que já tem uma propriedade comando de fechamento. Aqui está o código:

classe WorkspaceViewModel abstrato público: ViewModelBase {

    /// <summary>Returns the command that, when invoked, attempts to remove this workspace from the user interface.</summary>
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
                _closeCommand = new RelayCommand(param => OnRequestClose());

            return _closeCommand;
        }
    }
    RelayCommand _closeCommand;

    /// <summary>Raised when this workspace should be removed from the UI.</summary>
    public event EventHandler RequestClose;

    void OnRequestClose()
    {
        var handler = RequestClose;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    public bool Equals(WorkspaceViewModel other) {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other._closeCommand, _closeCommand) && base.Equals(other);
    }

    public override int GetHashCode() {
        unchecked {
            {
                return (base.GetHashCode() * 397) ^ (_closeCommand != null ? _closeCommand.GetHashCode() : 0);
            }
        }
    }
}

Você pode ver que o comando de fechamento é um RelayCommand, e que eu brincou com iguais para fazer o trabalho de teste de unidade.

@Merhdad Aqui está o teste de unidade que só funciona quando eu uso delegate.Method do Malandro na comparação de igualdade.

[TestFixture] WorkspaceViewModelTests classe pública { WorkspaceViewModel vm1 privado; WorkspaceViewModel privada vm2;

    private class TestableModel : WorkspaceViewModel
    {

    }

    [SetUp]
    public void SetUp() {
        vm1 = new TestableModel();
        vm1.RequestClose += OnWhatever;
        vm2 = new TestableModel();
        vm2.RequestClose += OnWhatever;
    }

    private void OnWhatever(object sender, EventArgs e) { throw new NotImplementedException(); }


    [Test]
    public void Equality() {
        Assert.That(vm1.CloseCommand.Equals(vm2.CloseCommand));
        Assert.That(vm1.Equals(vm2));
    }


}

----- edições mais recentes PARA USO MERHDAD "S IDEA

depurador posto para fora ? valueOfThisObject {Smack.Wpf.ViewModel.RelayCommand} base de {SharpArch.Core.DomainModel.ValueObject}: {} Smack.Wpf.ViewModel.RelayCommand _canExecute: null _execute: {Method = {_executeClose void (System.Object)}}

?valueToCompareTo
{Smack.Wpf.ViewModel.RelayCommand}
base {SharpArch.Core.DomainModel.ValueObject}: {Smack.Wpf.ViewModel.RelayCommand}
_canExecute: null
_execute: {Method = {Void _executeClose(System.Object)}}

?valueOfThisObject.Equals(valueToCompareTo)
false

Este é o resultado depois de mudar o código para:

    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
                _closeCommand = new RelayCommand(_executeClose);

            return _closeCommand;
        }
    }
    RelayCommand _closeCommand;

    void _executeClose(object param) {
        OnRequestClose();
    }
Foi útil?

Solução

Você está criando o delegado fora de funções anônimas ou algo assim? Estas são as regras exatas de igualdade delegado de acordo com C especificação # (§7.9.8):

operadores de igualdade de delegado

casos Dois delegados são considerados iguais a seguinte: Se qualquer um dos casos de delegado é null, são iguais se e somente se ambos são null .
Se os delegados têm o tipo diferente de tempo de execução eles nunca são iguais . Se ambos os casos delegado tem uma lista de invocação (§15.1), essas ocorrências são iguais se e somente se as suas listas de invocação são do mesmo comprimento, e cada entrada em um de lista de invocação é igual (como definido abaixo) para a entrada correspondente, em ordem, na lista da outra invocação. As seguintes regras governam a igualdade de entradas da lista de invocação:
Se duas entradas da lista de invocação tanto referem-se ao mesmo método static seguida, as entradas são iguais.
Se duas entradas da lista de invocação tanto referem-se a um método mesmo não static na mesmo objecto alvo (tal como definido pelos operadores de igualdade de referência), em seguida, as entradas são iguais.
entradas da lista de invocação produzido a partir de avaliação de anonymous-função-expressões semanticamente idênticos com o mesmo (possivelmente vazio) conjunto de instâncias de variáveis ??externas capturados está permitido (mas não é obrigatório ) para ser igual.

Assim, no seu caso, é possível que as instâncias de delegado estão se referindo ao mesmo método em dois objetos diferentes, ou seja referente a dois métodos anônimos.


UPDATE: Na verdade, o problema é que você não está passando a mesma referência de método quando você está chamando new RelayCommand(param => OnCloseCommand()). Afinal, a expressão lambda especificado aqui é realmente um método anônimo (você não está passando uma referência método para OnCloseCommand; você está passando uma referência a um método anônimo que leva um único parâmetro e chama OnCloseCommand). Como mencionado na última linha da cotação especificação acima, não é necessário que comparando os dois delegados retornar true.

Nota lateral: O getter da propriedade CloseCommand seria simplesmente chamado get_CloseCommand e não <get_CloseCommand>b__0. Este é o nome do método compilador gerado para o método método dentro get_CloseCommand anónimo (o getter CloseCommand). Isso prova ainda mais o ponto que eu mencionei acima.

Outras dicas

Eu não sei nada agora sobre outras linhas, mas o que se

CollectionAssert.AreEqual(_vm.ProjectActivities, models);

não só porque ReferenceEquality é usado?

Você substituído a comparação para RelayCommand mas não para ObservableCollection.

E parece que em caso de igualdade Delegados de Referência é usada também.

Tentar comparar por Delegate.Method vez.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top