【问题标题】:Using ReactiveCommand and WhenAny in inherited classes在继承的类中使用 ReactiveCommand 和 WhenAny
【发布时间】:2012-10-30 13:54:57
【问题描述】:

考虑在 ReactiveUI 中使用继承。 我有带有 DoSomethingCommand 的基本 ViewModel 类。此命令的“CanExecute”取决于属性 Prop1

public class A : ReactiveObject
{
    public int Prop1 { get {...} set {...} }
    public ReactiveCommand DoSomethingCommand { get; private set; }

    public A()
    {
        IObservable<bool> canDoSomething = this.WhenAny(vm => vm.Prop1, p1 => CanDoSomething());
        DoSomethingCommand = new ReactiveCommand(canDoSomething);
        DoSomethingCommand.Subscribe(x => DoSomething());
    }

    protected virtual bool CanDoSomething()
    {
        return ...
    }
}

在继承类中,此命令的“CanExecute”还取决于属性 Prop2

public class B : A
{
    public int Prop2 { get {...} set {...} }

    public B()
    {
        //Senseless code. For explanation only
        IObservable<bool> canDeleteExecute = this.WhenAny(vm => vm.Prop1, vm => vm.Prop2, (p1, p2) => CanDoSomething());
    }
}

创建命令并使“CanExecute”依赖于基类和继承类的属性的最佳做法是什么? 当然,我希望继承的类在基类中的“CanExecute”变得额外依赖AnotherProp属性时不应该改变。

【问题讨论】:

    标签: c# .net system.reactive reactiveui


    【解决方案1】:

    我会在我的基类中创建一个注册函数来注册 使用 CombineLatest 的 observables

        property IObservable<bool> CanDo;
    
        public IObservable<bool> RegisterCanDo( IObservable<bool> toRegister ){
                if ( CanDo == null ){
                        CanDo = toRegister;
                }else{
                        Cando = CanDo.CombineLatest(toRegister, (a,b) => a && b);
                }
    
        }
    

    所以现在任何你有一个 observable 想要成为你的一部分的地方 CanDo 链,您只需使用 RegisterCanDo 添加它

        public class B : A
        {
            public int Prop2 { get {...} set {...} }
    
            public B()
            {
                //Senseless code. For explanation only
                IObservable<bool> canDeleteExecute = this.WhenAny(vm => vm.Prop1, vm => vm.Prop2, (p1, p2) => CanDoSomething());
                RegisterCanDo(canDeleteExecute);
            }
        }
    

    【讨论】:

    • 我知道你打算用这个去哪里,但是没有办法取消注册CanDo,所以使用CombineLatest,你可能会确保CanDo 始终保持为假。也许RegisterCanDo 需要返回IDisposable 并且您需要一种机制来管理内部CanDo 状态?
    【解决方案2】:

    这正是您在上面想要的。但是要小心穿线。在组合值时添加和删除消息源时,我还没有使它成为线程安全的。

    您可以向集中器对象添加和删除消息源,并且可以观察集中器的真实值。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reactive.Disposables;
    using System.Reactive.Subjects;
    
    public class BoolObservableConcentrator : IObservable<bool>
    {
        private readonly Dictionary<IObservable<bool>, bool> dict = new Dictionary<IObservable<bool>, bool>();
    
        public IDisposable Register(IObservable<bool> observable)
        {
            dict.Add(observable, false);
            var d = observable.Subscribe(value =>
            {
                dict[observable] = value;
                Fire();
            });
            return Disposable.Create(() =>
            {
                d.Dispose();
                dict.Remove(observable);
                Fire();
            });
        }
    
        private readonly Subject<bool> subject = new Subject<bool>();
    
        private void Fire()
        {
            subject.OnNext(dict.Values.All(x => x));
        }
    
        public IDisposable Subscribe(IObserver<bool> observer)
        {
            return subject.Subscribe(observer);
        }
    }
    

    并对其进行测试:

    using System;
    using System.Reactive.Subjects;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    [TestClass]
    public class UnitTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            var s0 = new BehaviorSubject<bool>(false);
            var s1 = new BehaviorSubject<bool>(false);
    
            bool l = false;
    
            var c = new BoolObservableConcetrator();
    
            var r0 = c.RegisterSource(s0);
            var r1 = c.RegisterSource(s1);
    
            var s = c.Subscribe(v => l = v);
    
            Assert.AreEqual(false, l);
    
            s0.OnNext(true);
            Assert.AreEqual(false, l);
    
            s1.OnNext(true);
            Assert.AreEqual(true, l);
    
            s0.OnNext(false);
            Assert.AreEqual(false, l);
    
            // Removing one of the message sources should update the result
            r0.Dispose();
            Assert.AreEqual(true, l);
        }
    }
    

    【讨论】:

    • 原帖可以看这里:stackoverflow.com/revisions/13152784/1
    • @elshev 感谢对 c# 的重写。我经常根据代码库在 vb.net 和 c# 之间切换。我什至不确定为什么 MS 会打扰 VB.net,因为它与 c# 相同,因此我没有意识到 id 已发布 vb.net 代码,直到 id 提交 ;)
    • 但是你删除了我关于线程安全的警告。我还没有检查代码 fire combinelatest 但我猜他们在上面的代码没有的缓冲区上做了某种锁定
    • 非常感谢@bradgonesurfing。这是一个很好的解决方案,但我尝试了它并发现了两个缺点。 1. 每个继承者必须在每个 Register 方法中指定 CanExecute 2. 非触发 observable 的初始状态是未知的(在您的示例中为 false)。所以继承人必须担心。在其他地方 dict.Values.All(x => x) 将返回 false 直到所有 observables 触发。下面我编写了只收集属性依赖项的解决方案。 CanExecute 在基类中只指定一次。
    • 实际上它们不是假的,但我必须将字典初始化为字段。如果主题是 BehaviourSubjects,它们将在订阅时立即以正确的当前值触发。请注意,我不会订阅 BoolObservableConcentrator,直到所有源都已注册并且此时它已稳定。我不明白你的意思是让每个继承者都指定 CanExecute。这是一个实用程序类,不假设使用它的类如何实现继承。
    【解决方案3】:

    我已经为WhenAny 编写了扩展类。要让它像 ReactiveUI 中的 WhenAny 一样,还有很多工作要做,但现在对我来说已经足够了。一、看用法:

    public class A : ReactiveObject
    {
        public A()
        {
            //Using almost like WhenAny from ReactiveUI
            CanExecuteObservable = this.WhenAny(() => AProp, CanExecute);
            Command = new ReactiveCommand(CanExecuteObservable);
            Command.Subscribe(x => Execute());
        }
    
        protected CanExecuteObservable CanExecuteObservable { get; private set; }
        public ReactiveCommand Command { get; private set; }
    
        protected virtual bool CanExecute()
        {
            return AProp > 10;
        }
    
        private int aProp = 10;
        public int AProp { get { return aProp; } set { this.RaiseAndSetIfChanged(x => x.AProp, value); } }
    }
    
    public class B : A
    {
        public B()
        {
            //Add one more property dependency for CanExecute
            CanExecuteObservable.AddProperties(() => BProp);
        }
    
        private int bProp = 10;
        public int BProp { get { return bProp; } set { this.RaiseAndSetIfChanged(x => x.BProp, value); } }
    
        protected override bool CanExecute()
        {
            return base.CanExecute() && BProp > 100;
        }
    }
    

    实施:

    public static class WhenAnyExtensions
    {
        public static CanExecuteObservable WhenAny(this IReactiveNotifyPropertyChanged obj,
            IEnumerable<Expression<Func<object>>> expressions, Func<bool> func)
        {
            return new CanExecuteObservable(obj, expressions, func);
        }
    
        public static CanExecuteObservable WhenAny(this IReactiveNotifyPropertyChanged obj, Expression<Func<object>> property1, Func<bool> func)
        {
            return obj.WhenAny(new[] { property1 }, func);
        }
    
        public static CanExecuteObservable WhenAny(this IReactiveNotifyPropertyChanged obj, Expression<Func<object>> property1, Expression<Func<object>> property2, Func<bool> func)
        {
            return obj.WhenAny(new[] { property1, property2 }, func);
        }
    
        //etc...
    }
    
    public class CanExecuteObservable : IObservable<bool>
    {
        internal CanExecuteObservable(IReactiveNotifyPropertyChanged obj,
            IEnumerable<Expression<Func<object>>> expressions, Func<bool> func)
        {
            this.func = func;
            AddProperties(expressions);
            obj
                .Changed
                .Where(oc => propertyNames.Any(propertyName => propertyName == oc.PropertyName))
                .Subscribe(oc => Fire());
        }
    
        private readonly List<string> propertyNames = new List<string>();
        private readonly Func<bool> func;
    
        public void AddProperties(IEnumerable<Expression<Func<object>>> expressions)
        {
            foreach (var expression in expressions)
            {
                string propertyName = ReflectionHelper.GetPropertyNameFromExpression(expression);
                propertyNames.Add(propertyName);
            }
        }
    
        public void AddProperties(Expression<Func<object>> property1) { AddProperties(new[] { property1 }); }
        public void AddProperties(Expression<Func<object>> property1, Expression<Func<object>> property2) { AddProperties(new[] { property1, property2 }); }
        //etc...
    
        public void Clear()
        {
            propertyNames.Clear();
        }
    
        private readonly Subject<bool> subject = new Subject<bool>();
    
        private void Fire()
        {
            subject.OnNext(func());
        }
    
        public IDisposable Subscribe(IObserver<bool> observer)
        {
            return subject.Subscribe(observer);
        }
    }
    

    在这种情况下,用于从表达式中获取属性名称的辅助类是无趣的:

    public class ReflectionHelper
    {
        public static string GetPropertyNameFromExpression<T>(Expression<Func<T>> property) 
        {
            var lambda = (LambdaExpression)property;
            MemberExpression memberExpression;
    
            if (lambda.Body is UnaryExpression) 
            {
                var unaryExpression = (UnaryExpression)lambda.Body;
                memberExpression = (MemberExpression)unaryExpression.Operand;
            } 
            else 
            {
                memberExpression = (MemberExpression)lambda.Body;
            }
            return memberExpression.Member.Name;
        }
    }
    

    【讨论】:

      【解决方案4】:

      为什么不在派生构造函数中替换它:

      更新:这是一个考虑到原始版本的版本

      public class A
      {
          public ReactiveCommand SomeCommand { get; protected set; }
      
          public A()
          {
              SomeCommand = new ReactiveCommand(this.WhenAny(x => x.SomeProp, ...));
          }
      }
      
      
      public class B : A
      {
          public A()
          {
              var newWhenAny = this.WhenAny(x => x.SomeOtherProp, ...);
      
              var canExecute = SomeCommand == null ?
                  newWhenAny :
                  SomeCommand.CanExecuteObservable.CombineLatest(newWhenAny,(oldCommand, whenAny) => oldCommand && whenAny);
      
              SomeCommand = new ReactiveCommand(canExecute);
          }
      }
      

      【讨论】:

      • 因为我想要: 1. SomeCommand 同时依赖于 SomeProp 和 SomeOtherProp。 2.如果SomeCommand依赖A类中的SomeProp2,SomeProp3等,B类不会打扰
      猜你喜欢
      • 1970-01-01
      • 2013-05-26
      • 2018-05-23
      • 2019-08-31
      • 1970-01-01
      • 2020-12-21
      • 2023-03-10
      • 1970-01-01
      • 2010-11-08
      相关资源
      最近更新 更多