首页 > 解决方案 > ICommand 实现中不可复制的 System.InvalidCastException(Roslyn 错误?)

问题描述

我有一个非常奇怪的问题,我的测试失败了:

System.InvalidCastException:无法将“<>c__DisplayClass18_0”类型的对象转换为“System.ComponentModel.INotifyPropertyChanged”类型。

但是,当我运行“调试测试”时,测试是绿色的,无论我是从单元测试还是实时测试“调试”运行调试。因此,调查起来很棘手。ICommand此外,我在应用程序中使用这些实现从未遇到任何问题。

我不应该有任何Exception(我没有任何单元测试,我有实时测试,并且在这种情况下 VS 没有达到(破坏)异常,即使它正在发生)。我该如何前进?

演员表问题发生在ListenForNotificationFrom((INotifyPropertyChanged) _executeDelegate.Target);课堂DelegateCommandListen上。

编辑:无论Action<T>是私有命名函数 (1) 还是局部函数 (2) 或 lambda (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);
}

这是我测试视图模型的方法:

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

视图模型:

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

标签: c#wpfvisual-studiounit-testing

解决方案


根据 Artur Spychaj (MSFT) 的说法,测试失败是因为 LUT(实时单元测试)修改了 lambda 以捕获覆盖信息,并且编译器将 lambda 移动到一个单独的类中。this解决方法是作为单独的参数传递DelegateCommandListen.


推荐阅读