Question

Je pense que c'est la question, de toute façon. J'utilise une RelayCommand, qui décore une ICommand avec deux délégués. L'un est prédicat pour _canExecute et l'autre est Action pour la méthode _execute.

--- Motivation de fond -

La motivation est liée aux tests unitaires ViewModels pour une présentation WPF . Une tendance fréquente est que j'ai un ViewModel qui a un ObservableCollection, et je veux un test unitaire pour prouver que les données de cette collection correspondent à ce que j'attendais avec certaines données source (qui doivent également être converties en une collection de ViewModels). Même si les données des deux collections ont la même apparence dans le débogueur, il semble que le test ait échoué en raison d'un échec d'égalité sur RelayCommand du ViewModel. Voici un exemple de test unitaire défaillant:

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

--- Retour à l'égalité des délégués ----

Voici le code de RelayCommand - il s’agit essentiellement d’une arnaque directe de l’idée de Josh Smith, avec une implémentation pour l’égalité que j’ai ajoutée pour tenter de résoudre ce problème:

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

}

Dans un test unitaire dans lequel j'ai effectivement défini le délégué _execute sur la même méthode (_canExecute est nul dans les deux cas), le test unitaire échoue à cette ligne:

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

Sortie du débogueur:

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

Quelqu'un peut-il expliquer ce qui me manque et la solution?

---- REMARQUES MODIFIÉES ----

Comme Mehrdad l'a souligné, la commande get_CloseCommand de la session de débogage a un aspect un peu étrange au début. C’est vraiment une propriété, mais cela explique pourquoi l’égalité du délégué est problématique si j’ai besoin de faire des astuces pour que cela fonctionne.

MVVM a notamment pour objectif d’exposer tout ce qui peut être utile dans une présentation en tant que propriétés, afin que vous puissiez utiliser la liaison WPF. La classe particulière que je testais a un WorkspaceViewModel dans sa hiérarchie, qui est juste un ViewModel qui a déjà une propriété de commande close. Voici le code:

classe abstraite publique 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);
            }
        }
    }
}

Vous pouvez voir que la commande close est une RelayCommand, et que je me suis servi de sonkey pour que le test unitaire fonctionne.

@ Merhdad Voici le test unitaire qui ne fonctionne que lorsque j'utilise le delegate.Method de Trickster dans la comparaison d'égalité.

[TestFixture]     Classe publique WorkspaceViewModelTests     {         privé WorkspaceViewModel vm1;         v WorkspaceViewModel privé;

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


}

----- DERNIERS MODIFICATIONS POUR UTILISER MERHDAD & "S IDEA"

debugger out put     ? 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

Voici le résultat après avoir changé le code en:

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

            return _closeCommand;
        }
    }
    RelayCommand _closeCommand;

    void _executeClose(object param) {
        OnRequestClose();
    }
Était-ce utile?

La solution

Créez-vous le délégué à partir de fonctions anonymes ou de quelque chose? Voici les règles d’égalité des délégués selon la spécification C # (& # 167; 7.9.8):

  

Déléguer des opérateurs d'égalité

     

Deux instances de délégué sont considérées comme égales comme suit:   Si l'une des instances de délégué est null, elles sont égales si et seulement si elles sont toutes deux static .
  Si les délégués ont un type d'exécution différent , ils ne sont jamais égaux .   Si les deux instances de délégué ont une liste d'appels (& # 167; 15.1), elles sont égales si et seulement si leurs listes d'appels ont la même longueur, et chaque entrée dans une même liste d'appels & # 8217; s est égal (comme défini ci-dessous) à l'entrée correspondante, dans l'ordre, dans l'autre liste d'invocation & # 8217; s.   Les règles suivantes régissent l’égalité des entrées de la liste d’invocations:
  Si deux entrées de la liste d'appel sont toutes deux renvoyées à la même new RelayCommand(param => OnCloseCommand()) méthode , les entrées sont égales.
  Si deux entrées de la liste d'appel sont toutes deux font référence à la même méthode non - OnCloseCommand sur le même objet cible (défini par les opérateurs d'égalité de référence), les entrées sont égales.
  Les entrées de liste d'invocation produites à partir de l'évaluation d'expressions de fonction anonymes sémantiquement identiques avec le même ensemble (éventuellement vide) d'instances de variable externe capturées sont autorisées (mais non obligatoires). ) pour être égal.

Dans votre cas, il est donc possible que les instances de délégué fassent référence à la même méthode dans deux objets différents ou à deux méthodes anonymes.

UPDATE: En effet, le problème est que vous ne transmettez pas la même référence de méthode lorsque vous appelez true. Après tout, l'expression lambda spécifiée ici est en fait une méthode anonyme (vous ne transmettez pas une référence de méthode à CloseCommand; vous transmettez une référence à une méthode anonyme qui prend un seul paramètre et appelle get_CloseCommand). Comme indiqué à la dernière ligne de la citation de spécification ci-dessus, il n'est pas nécessaire que la comparaison de ces deux délégués renvoie <get_CloseCommand>b__0.

Note latérale: Le getter de la propriété <=> s'appellera simplement <=> et non <=>. Ceci est le nom de méthode généré par le compilateur pour la méthode anonyme à l'intérieur de la méthode <=> (le <=> getter). Cela prouve encore le point que j'ai mentionné ci-dessus.

Autres conseils

Je ne sais plus rien des autres lignes, mais que se passe-t-il si

CollectionAssert.AreEqual(_vm.ProjectActivities, models);

échoue simplement parce que ReferenceEquality est utilisé?

Vous avez ignoré la comparaison pour RelayCommand mais pas pour ObservableCollection.

Et il semble que dans le cas de la référence des délégués, l'égalité est également utilisée.

Essayez plutôt de comparer avec Delegate.Method.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top