Невоспроизводимая исключительная ситуация System.InvalidCastException в реализации ICommand (ошибка Roslyn?) - PullRequest
0 голосов
/ 11 декабря 2018

У меня возникла очень любопытная проблема, в которой у меня не удается выполнить тесты с:

System.InvalidCastException: невозможно привести объект типа '<> c__DisplayClass18_0' к типу System.ComponentModel.INotifyPropertyChanged '.

Однако, когда я запускаю "тест отладки", тесты зеленые, если я запускаю отладку из модульных тестов или живых тестов "отладка".Поэтому сложно исследовать.Более того, у меня никогда не было проблем с использованием этих ICommand реализаций в приложении.

У меня не должно быть никаких Exception (у меня их нет в модульных тестах, у меня в живых тестах иVS не достигает (нарушает) исключение в этом случае, даже если это происходит).Как мне двигаться дальше?

Проблема приведения происходит в ListenForNotificationFrom((INotifyPropertyChanged) _executeDelegate.Target); в классе DelegateCommandListen.

РЕДАКТИРОВАТЬ: * * * * * * * Action<T> является частной именованной функцией (1) или локальной функции (2) или лямбда-функции (2), ее свойство Target четко определено и должно быть приведено к INotifyPropertyChanged.Где (1) работает как для живых тестов, так и для модульных тестов, и (2) работает только в модульных тестах.

Моя ICommand реализация:

public class DelegateCommandListen : ICommand
{
    private readonly List<WeakReference> _controlEvent;
    private Action<object> _executeDelegate;

    public DelegateCommandListen(Action<object> executeDelegate, Predicate<object> canExecuteDelegate)
    {
        _controlEvent = new List<WeakReference>();
        ExecuteDelegate = executeDelegate;
        CanExecuteDelegate = canExecuteDelegate;
    }

    public Predicate<object> CanExecuteDelegate { get; set; }

    public Action<object> ExecuteDelegate
    {
        get { return _executeDelegate; }
        set
        {
            _executeDelegate = value;
            ListenForNotificationFrom((INotifyPropertyChanged) _executeDelegate.Target);
        }
    }

    public void RaiseCanExecuteChanged()
    {
        if (_controlEvent != null && _controlEvent.Count > 0)
            _controlEvent.ForEach(ce => { ((EventHandler) ce.Target)?.Invoke(null, EventArgs.Empty); });
    }

    public DelegateCommandListen ListenOn<TObservedType, TPropertyType>
        (TObservedType viewModel, Expression<Func<TObservedType, TPropertyType>> propertyExpression)
        where TObservedType : INotifyPropertyChanged
    {
        var propertyName = GetPropertyName(propertyExpression);
        viewModel.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == propertyName) RaiseCanExecuteChanged();
        };
        return this;
    }

    public void ListenForNotificationFrom<TObservedType>(TObservedType viewModel)
        where TObservedType : INotifyPropertyChanged
    {
        viewModel.PropertyChanged += (s, e) => RaiseCanExecuteChanged();
    }

    private static string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> expression)
        where T : INotifyPropertyChanged
    {
        var lambda = expression as LambdaExpression;
        var memberInfo = GetMemberExpression(lambda).Member;
        return memberInfo.Name;
    }

    private static MemberExpression GetMemberExpression(LambdaExpression lambda)
    {
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression body)
        {
            var unaryExpression = body;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
            memberExpression = lambda.Body as MemberExpression;
        return memberExpression;
    }

    public bool CanExecute(object parameter) => CanExecuteDelegate == null || CanExecuteDelegate(parameter);

    public event EventHandler CanExecuteChanged
    {
        add
        {
            CommandManager.RequerySuggested += value;
            _controlEvent.Add(new WeakReference(value));
        }
        remove
        {
            CommandManager.RequerySuggested -= value;
            _controlEvent.Remove(_controlEvent.Find(r => (EventHandler) r.Target == value));
        }
    }

    public void Execute(object parameter) => ExecuteDelegate?.Invoke(parameter);
}

Вот как я тестируюviewmodel:

[TestMethod]
public void NoTarget()
{
    var sut = new DummyViewModel();
    Assert.IsFalse(sut.IsSelected);
    Assert.IsFalse(sut.ListenWithoutTargetCommand.CanExecute(null));
    sut.IsSelected = true;
    Assert.IsTrue(sut.ListenWithoutTargetCommand.CanExecute(null));
}

The ViewModel:

public class DummyViewModel : INotifyPropertyChanged
{
    private ICommand _listenWith1TargetCommand;
    private bool _isSelected;

    public string Result { get; set; }

    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            if (value == _isSelected) return;
            _isSelected = value;
            OnPropertyChanged();
        }
    }

    public ICommand ListenWith1TargetCommand
    {
        get
        {
            return _listenWith1TargetCommand ?? (_listenWith1TargetCommand = new DelegateCommandListen(
                           s => { Result = "Executing listen command 1"; }, // lambda|local function|named function
                           s => IsSelected)
                       .ListenOn(this, o => o.IsSelected));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

Полный источник: https://github.com/mprevot/ReproLiveTests

Ответы [ 2 ]

0 голосов
/ 22 августа 2019

Согласно Артуру Спичаю (MSFT), тесты не пройдены, потому что LUT (Live Unit Testing) изменяет лямбда-выражения для сбора информации о покрытии, а компилятор перемещает лямбду в отдельный класс.Обходной путь должен передать this как отдельный параметр DelegateCommandListen.

0 голосов
/ 12 декабря 2018

Мы, смертные, обычно не замечаем ошибок Рослина :) Поэтому важно попытаться решить проблему под рукой, исследуя, а не обвиняя инструменты :) Я думаю, что Visual Studio говорит вам точную проблему.

System.InvalidCastException: Невозможно привести объект типа '<> c__DisplayClass18_0' к типу 'System.ComponentModel.INotifyPropertyChanged'.

Более того, вы почти решили проблему самостоятельно.Вы упоминаете, что анонимная лямбда не работает, но работают именованные функции.

Позвольте нам решить вашу проблему одну за другой.Прежде всего, что такое свойство Action<T>.Target.Просто скопируйте и вставьте из Visual Studio.

Объект, для которого текущий делегат вызывает метод экземпляра, если делегат представляет метод экземпляра;null, если делегат представляет статический метод.

Итак, что, по вашему мнению, происходит, когда вы передаете анонимную лямбду?Как мы передаем анонимную лямбду в первую очередь?Ну, вы уже знаете, поэтому я просто освежу память каждого.Компилятор в фоновом режиме создает класс, в вашем случае <>c__DisplayClass18_0 и устанавливает анонимную лямбду в качестве свойств указанного сгенерированного класса и передает ее так, как будто они называются функциями надлежащего класса!Это важно.

Что происходит в вашем случае тогда?Когда вы передаете анонимную лямбду

ListenForNotificationFrom((INotifyPropertyChanged) _executeDelegate.Target);

_executeDelegate's.Target - это класс, сгенерированный компилятором '<>c__DisplayClass18_0', который, конечно, не реализует System.ComponentModel.INotifyPropertyChanged.Это то, что происходит.Это не ошибка Рослина, на самом деле это именно то, что и должно быть:)

Редактировать: просто добавив скриншот отладки.Это на японском, но я думаю, вы можете ясно видеть, что это то же самое, что и сообщение ОП.enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...