public static IDisposable BindPropertyChanged <T>(this IOperationController controller, INotifyPropertyChanged owner, string propertyName, bool autoMerge = true)
        {
            var prevValue         = FastReflection.GetProperty <T>(owner, propertyName);
            var callFromOperation = false;

            owner.PropertyChanged += PropertyChanged;

            return(new Disposer(() => owner.PropertyChanged -= PropertyChanged));

            // local function
            void PropertyChanged(object sender, PropertyChangedEventArgs args)
            {
                if (callFromOperation)
                {
                    return;
                }

                if (args.PropertyName == propertyName)
                {
                    callFromOperation = true;
                    T   newValue  = FastReflection.GetProperty <T>(owner, propertyName);
                    var operation = owner
                                    .GenerateAutoMergeOperation(propertyName, newValue, prevValue, $"{sender.GetHashCode()}.{propertyName}", Operation.DefaultMergeSpan);

                    if (autoMerge)
                    {
                        operation = operation.Merge(controller);
                    }

                    operation
                    .AddPreEvent(() => callFromOperation  = true)
                    .AddPostEvent(() => callFromOperation = false);

                    prevValue = newValue;

                    controller.Push(operation);
                    callFromOperation = false;
                }
            }
        }