.ToObservable() 运算符是该时间点集合的一次性快照。任何未来添加到集合中的内容都不会通过 observable。
所以你需要使用一些可以让你观察到变化的东西。
为了让您最直接地了解您当前使用的内容,我建议将 ICollection<IValidationRule<TValue>> 更改为 System.Collections.ObjectModel.ObservableCollection<IValidationRule<TValue>>。请注意,这不是您应该这样做的方式,但我想为您提供一些您可以立即看到的与您问题中的代码相关的内容。
然后您可以这样编写代码:
public class ValidatableObject<TValue>
{
public bool IsValid { get; private set; } = true;
public TValue Value { get; }
public System.Collections.ObjectModel.ObservableCollection<IValidationRule<TValue>> Rules { get; }
public ValidatableObject(TValue value)
{
Value = value;
Rules = new ObservableCollection<IValidationRule<TValue>>();
Observable
.FromEventPattern<
System.Collections.Specialized.NotifyCollectionChangedEventHandler,
System.Collections.Specialized.NotifyCollectionChangedEventArgs>(
h => Rules.CollectionChanged += h,
h => Rules.CollectionChanged -= h)
.Select(ep => Rules.All(rule => rule.Check(Value)))
.Subscribe(b => IsValid = b);
}
}
请注意,我已使用 FromEventPattern 从 CollectionChanged 事件构建一个可观察对象,但我仍在使用 LINQ to Objects 来评估规则。您在代码中执行此操作的方式只能将IsValid 设置为true。我的做法是让它根据规则设置为true 或false。
我创建了几个测试规则:
public class ValidationRuleTrue<T> : IValidationRule<T>
{
public bool Check(T value) => true;
}
public class ValidationRuleFalse<T> : IValidationRule<T>
{
public bool Check(T value) => false;
}
然后我运行了这个:
void Main()
{
var theObject = new object();
var v = new ValidatableObject<object>(theObject);
Console.WriteLine(v.IsValid);
v.Rules.Add(new ValidationRuleTrue<object>());
Console.WriteLine(v.IsValid);
v.Rules.Add(new ValidationRuleFalse<object>());
Console.WriteLine(v.IsValid);
}
我明白了:
真的
真的
错误的
这是意料之中的。
但是,您的代码结构不适合使用 Rx。您的查询可能很容易被构造为将可观察管道推送到不同的线程。如果发生这种情况,代码将不会产生正确的结果。
举个例子,试试这个简单的改变:
Observable
.FromEventPattern<
System.Collections.Specialized.NotifyCollectionChangedEventHandler,
System.Collections.Specialized.NotifyCollectionChangedEventArgs>(
h => Rules.CollectionChanged += h,
h => Rules.CollectionChanged -= h)
.Select(ep => Rules.All(rule => rule.Check(Value)))
.ObserveOn(Scheduler.Default)
.Subscribe(b => IsValid = b);
现在,当我运行相同的测试代码时,我得到了这个:
真的
真的
真的
基本上,最终的Console.WriteLine(v.IsValid); 在Scheduler.Default 上运行的可观察对象更新IsValid 之前运行。你有一个竞争条件。
如果您打算开始更多地使用 Rx,您可以轻松地创建一个使用在不同线程上运行的调度程序的查询。所以你需要以一种对 Rx 有意义的方式来编写你的类。
试试这样:
public class ValidatableObject<TValue>
{
public IObservable<bool> IsValid { get; private set; }
public TValue Value { get; }
public IEnumerable<IValidationRule<TValue>> Rules { get; }
private ObservableCollection<IValidationRule<TValue>> _rules;
public IDisposable AddRule(IValidationRule<TValue> rule)
{
_rules.Add(rule);
return Disposable.Create(() => _rules.Remove(rule));
}
public ValidatableObject(TValue value)
{
Value = value;
_rules = new ObservableCollection<IValidationRule<TValue>>();
this.IsValid =
Observable
.FromEventPattern<
NotifyCollectionChangedEventHandler,
NotifyCollectionChangedEventArgs>(
h => _rules.CollectionChanged += h,
h => _rules.CollectionChanged -= h)
.Select(ep => _rules.All(rule => rule.Check(Value)))
.ObserveOn(Scheduler.Default);
}
}
注意两点。
(1) IsValid 现在是 IObservable<bool>,它摆脱了竞争条件。
(2) 规则不公开为ObservableCollection 以供整个外部世界操作,而是有一个单独的AddRule 方法来添加规则和IDisposable 来删除规则。这意味着只有添加规则的代码才能删除它。它得到了更好的控制。您的代码并非 100% 需要工作 - 您当然可以公开 ObservableCollection - 但这是考虑 Rx 世界中操作的好方法。
现在我可以这样编写测试代码了:
void Main()
{
var theObject = new object();
var v = new ValidatableObject<object>(theObject);
var subscription = v.IsValid.Subscribe(isValid => Console.WriteLine(isValid));
var rule1 = v.AddRule(new ValidationRuleTrue<object>());
var rule2 = v.AddRule(new ValidationRuleFalse<object>());
rule2.Dispose(); //remove `rule2`
}
我得到了预期值:
真的
错误的
真的
这是我实现观察值本身的基本方法:
public interface IValidationRule<T> where T : INotifyPropertyChanged
{
bool Check(T value);
}
public class ValidatableObject<TValue> where TValue : INotifyPropertyChanged
{
public IObservable<bool> IsValid { get; private set; }
public TValue Value { get; }
public IEnumerable<IValidationRule<TValue>> Rules { get; }
private ObservableCollection<IValidationRule<TValue>> _rules;
public IDisposable AddRule(IValidationRule<TValue> rule)
{
_rules.Add(rule);
return Disposable.Create(() => _rules.Remove(rule));
}
public ValidatableObject(TValue value)
{
Value = value;
_rules = new ObservableCollection<IValidationRule<TValue>>();
var rulesChanged =
Observable
.FromEventPattern<
NotifyCollectionChangedEventHandler,
NotifyCollectionChangedEventArgs>(
h => _rules.CollectionChanged += h,
h => _rules.CollectionChanged -= h)
.Select(ep => Unit.Default);
var valueChanged =
Observable
.FromEventPattern<
PropertyChangedEventHandler,
PropertyChangedEventArgs>(
h => value.PropertyChanged += h,
h => value.PropertyChanged -= h)
.Select(ep => Unit.Default);
this.IsValid =
Observable
.Merge(rulesChanged, valueChanged)
.Select(ep => _rules.All(rule => rule.Check(Value)))
.ObserveOn(Scheduler.Default);
}
}