{"record":{"id":"ff31b507ddef9b03","repo":"AvaloniaUI/Avalonia","slug":"cannot-call-asobservable-on-a-to-binding-expressio","errorCode":null,"errorMessage":"Cannot call AsObservable on a to binding expression which is already instantiated on an AvaloniaObject.","messagePattern":"Cannot call AsObservable on a to binding expression which is already instantiated on an AvaloniaObject\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs","lineNumber":239,"sourceCode":"    /// </exception>\n    /// <remarks>\n    /// This method is mostly here for unit testing and we may want to remove it in future. In\n    /// particular its usefulness is limited in that it preserves the semantics of binding\n    /// expressions as expected by unit tests, not necessarily the semantics that will be used\n    /// when the expression is used as an <see cref=\"IValueEntry\"/> instantiated in a\n    /// <see cref=\"ValueStore\"/>. Unit tests should be migrated to not test the behaviour of\n    /// binding expressions through an observable, and instead test the behaviour of the binding\n    /// when applied to an <see cref=\"AvaloniaObject\"/>.\n    /// \n    /// A binding expression may only act as an observable or as a binding expression targeting an\n    /// AvaloniaObject, not both.\n    /// </remarks>\n    internal IAvaloniaSubject<object?> ToObservable(AvaloniaObject? target = null)\n    {\n        if (_sink is ObservableSink s)\n            return s;\n        if (_sink is not null)\n            throw new InvalidOperationException(\n                \"Cannot call AsObservable on a to binding expression which is already \" +\n                \"instantiated on an AvaloniaObject.\");\n\n        var o = new ObservableSink(this);\n        _sink = o;\n        _target = target is not null ? new(target) : null;\n        return o;\n    }\n\n    /// <summary>\n    /// When overridden in a derived class, writes the specified value to the binding source if\n    /// possible.\n    /// </summary>\n    /// <param name=\"value\">The value to write.</param>\n    /// <returns>\n    /// True if the value could be written to the binding source; otherwise false.\n    /// </returns>\n    internal virtual bool WriteValueToSource(object? value) => false;","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/AvaloniaUI/Avalonia/blob/11c542726898ae954a1ef668c65ec79ec92ab17d/src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs#L221-L257","documentation":"A binding expression may serve either as an observable (ToObservable) or be instantiated on an AvaloniaObject, but not both. ToObservable() throws when the expression already has a non-ObservableSink, meaning it is attached to a target. This guards the mutually-exclusive usage noted in the remarks.","triggerScenarios":"Calling ToObservable()/AsObservable on a binding expression that has already been attached to an AvaloniaObject target via AttachCore (i.e. `_sink` is set to a sink that is not an ObservableSink).","commonSituations":"Legacy unit tests that apply a binding to a control and then also try to observe it as an IObservable. Migrating test code that mixed observable and binding-expression usage on the same instance.","solutions":["Use a fresh binding expression instance for ToObservable, separate from the one applied to an AvaloniaObject.","Rewrite the test to observe the bound AvaloniaObject's property changes (e.g. via `control.GetObservable(prop)`) instead of calling ToObservable on an attached expression.","If you need observable semantics, do not attach the expression to a target first."],"exampleFix":"// before (throws - same expr attached and observed):\nvar expr = binding.CreateInstance(target, prop, anchor);\n((UntypedBindingExpressionBase)expr).ToObservable(target);\n\n// after (separate instances):\nvar forTarget = binding.CreateInstance(target, prop, anchor);\ntarget.Bind(prop, binding);\n// observe the target property directly:\ntarget.GetObservable(prop).Subscribe(...);","handlingStrategy":"validation","validationCode":"// Do not mix: choose observable OR attached-to-target per instance.\nif (expr is UntypedBindingExpressionBase b && /* already attached */) { /* use target observable */ }","typeGuard":"static bool IsObservableOnly(UntypedBindingExpressionBase e) => e._sink is null or ObservableSink; // (conceptual; sink is private)","tryCatchPattern":null,"preventionTips":["Never attach the same expression to a target and also call ToObservable.","Observe bound targets via control.GetObservable(property).","Use a fresh expression instance for observable tests."],"tags":["bindings","observable","bindingexpression","invalidoperation"],"backgroundTag":null,"analyzedSha":"11c542726898ae954a1ef668c65ec79ec92ab17d","analyzedAt":"2026-08-13T11:57:40.261Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}