【发布时间】:2018-12-11 17:58:03
【问题描述】:
我有一个非常奇怪的问题,我的测试失败了:
System.InvalidCastException:无法转换类型的对象 'c__DisplayClass18_0' 键入 'System.ComponentModel.INotifyPropertyChanged'。
但是,当我运行“调试测试”时,测试是绿色的,无论我是从单元测试还是实时测试“调试”运行调试。因此,调查起来很棘手。此外,我在应用程序中使用那些 ICommand 实现从未遇到任何问题。
我不应该有任何Exception(我没有任何单元测试,我有实时测试,并且在这种情况下 VS 没有达到(破坏)异常,即使它正在发生) .我应该如何前进?
演员表问题发生在DelegateCommandListen类中的ListenForNotificationFrom((INotifyPropertyChanged) _executeDelegate.Target);。
编辑: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));
}
}
【问题讨论】:
-
调查起来并不难。看看错误信息告诉你什么。
<>c__DisplayClass18_0似乎是一个匿名类型,它说它无法转换为INotifyPropertyChanged -
@RobertHarvey 1) VS/Roslyn 没有这么说(你的“它出现”是“文化”而不是逻辑)2)我不知道在没有中断的情况下你如何准确调查。
-
@RobertHarvey 和 Ash 你看到源了吗?我举了一个完整的例子。
-
不在我的电脑上,所以不能检查这个,但是......在你初始化
_listenWith1TargetCommand的地方,尝试用对类方法的引用替换匿名委托。这些匿名委托可能正在编译为您在异常中看到的匿名类型(您可以使用 ILSpy/Reflector 之类的程序集检查工具进行检查)。所以匿名类型不能转换为INotifyPropertyChanged也就不足为奇了。而对 ViewModel 上的方法的引用可能会给出预期的目标。 -
@EmrahSüngü 好吧,那你应该去试试。有用。 OP 说当他进行单元测试时它不起作用。
标签: c# wpf visual-studio unit-testing