Domanda

Penso che questa sia la domanda, comunque. Sto usando un RelayCommand, che decora un comando IC con due delegati. Uno è Predicato per _canExecute e l'altro è Azione per il metodo _execute.

--- Motivazione di fondo -

La motivazione ha a che fare con ViewModels di unit testing per una presentazione WPF . Un modello frequente è che ho un ViewModel che ha un ObservableCollection e voglio un unit test per dimostrare che i dati in quella raccolta sono quelli che mi aspetto dati alcuni dati di origine (che devono anche essere convertiti in una raccolta di ViewModels). Anche se i dati in entrambe le raccolte sembrano uguali nel debugger, sembra che il test fallisca a causa di un errore di uguaglianza nel comando Relay di ViewModel. Ecco un esempio del test unitario fallito:

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

--- Torna all'uguaglianza dei delegati ----

Ecco il codice per il RelayCommand - fondamentalmente è una fregatura diretta dell'idea di Josh Smith, con un'implementazione per l'uguaglianza che ho aggiunto nel tentativo di risolvere questo 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);
        }
    }

}

In un unit test in cui ho effettivamente impostato il delegato _execute sullo stesso metodo (_canExecute è nullo in entrambi i casi), il test unitario ha esito negativo su questa riga:

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

Output del debugger:

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

Qualcuno può spiegare cosa mi sto perdendo e qual è la correzione?

---- OSSERVAZIONI MODIFICATE ----

Come sottolineato da Mehrdad, get_CloseCommand della sessione di debug sembra inizialmente un po 'strano. È davvero solo una proprietà, ma solleva il punto sul perché l'uguaglianza del delegato è problematica se devo fare dei trucchi per farlo funzionare.

Parte del punto di MVVM è esporre come proprietà qualsiasi cosa possa essere utile in una presentazione, in modo da poter usare l'associazione WPF. La particolare classe che stavo testando ha un WorkspaceViewModel nella sua gerarchia, che è solo un ViewModel che ha già una proprietà di comando close. Ecco il codice:

classe astratta pubblica WorkspaceViewModel: 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);
            }
        }
    }
}

Puoi vedere che il comando close è un RelayCommand e che ho fatto una scimmia con uguale per far funzionare il test unitario.

@Merhdad Ecco il test unitario che funziona solo quando uso il delegato di Trickster. Metodo nel confronto sull'uguaglianza.

[TestFixture]     classe pubblica WorkspaceViewModelTests     {         WorkspaceViewModel privato vm1;         Private WorkspaceViewModel 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));
    }


}

----- ULTIME MODIFICHE PER UTILIZZARE MERHDAD " S IDEA

debugger messo fuori     ? valueOfThisObject     {} Smack.Wpf.ViewModel.RelayCommand     base {SharpArch.Core.DomainModel.ValueObject}: {Smack.Wpf.ViewModel.RelayCommand}     _canExecute: null     _execute: {Method = {Void _executeClose (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

Questo è il risultato dopo aver modificato il codice in:

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

            return _closeCommand;
        }
    }
    RelayCommand _closeCommand;

    void _executeClose(object param) {
        OnRequestClose();
    }
È stato utile?

Soluzione

Stai creando il delegato da funzioni anonime o qualcosa del genere? Queste sono le esatte regole di uguaglianza dei delegati secondo la specifica C # (& # 167; 7.9.8):

  

Delegate operatori di uguaglianza

     

Due istanze delegate sono considerate uguali come segue:   Se una delle istanze delegate è null, sono uguali se e solo se entrambe sono static .   Se i delegati hanno un tipo di runtime diverso non sono mai uguali .   Se entrambe le istanze delegate hanno un elenco di invocazioni (& # 167; 15.1), tali istanze sono uguali se e solo se i loro elenchi di invocazione hanno la stessa lunghezza e ciascuna voce in un elenco di invocazioni & # 8217; è uguale (come definito di seguito) alla voce corrispondente, in ordine, nell'elenco di invocazioni dell'altro & # 8217; s.   Le seguenti regole regolano l'uguaglianza delle voci dell'elenco di invocazione:
  Se due voci dell'elenco di invocazione entrambe si riferiscono allo stesso new RelayCommand(param => OnCloseCommand()) metodo , le voci sono uguali.
  Se due voci dell'elenco di invocazioni entrambe si riferiscono allo stesso metodo non - OnCloseCommand sullo stesso oggetto di destinazione (come definito dagli operatori di uguaglianza di riferimento), le voci sono uguali.
  Le voci dell'elenco di invocazioni prodotte dalla valutazione di espressioni di funzioni anonime semanticamente identiche con lo stesso insieme (possibilmente vuoto) di istanze variabili esterne acquisite sono consentite (ma non necessarie ) essere uguali.

Quindi, nel tuo caso, è possibile che le istanze delegate si riferiscano allo stesso metodo in due oggetti diversi o si riferiscano a due metodi anonimi.


AGGIORNAMENTO: In effetti, il problema è che non si sta passando lo stesso riferimento di metodo quando si chiama true. Dopotutto, l'espressione lambda qui specificata è in realtà un metodo anonimo (non si passa un riferimento di metodo a CloseCommand; si passa un riferimento a un metodo anonimo che accetta un singolo parametro e chiama get_CloseCommand). Come menzionato nell'ultima riga del preventivo delle specifiche sopra, non è necessario che il confronto di questi due delegati restituisca <get_CloseCommand>b__0.

Nota a margine: Il getter della proprietà <=> verrebbe semplicemente chiamato <=> e non <=>. Questo è il nome del metodo generato dal compilatore per il metodo anonimo all'interno del metodo <=> (il <=> getter). Ciò dimostra ulteriormente il punto che ho menzionato sopra.

Altri suggerimenti

Non so nulla ora di altre righe ma cosa succede se

CollectionAssert.AreEqual(_vm.ProjectActivities, models);

fallisce solo perché viene utilizzato ReferenceEquality?

Hai superato il confronto per RelayCommand ma non per ObservableCollection.

E sembra che anche nel caso dei delegati venga usata l'uguaglianza di riferimento.

Prova invece a confrontare con Delegate.Method.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top