diff --git a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs
index 3beb6bce..eb1db12e 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs
@@ -720,6 +720,10 @@ internal static void EmitInlineObservation(
/// for inner segments, so reusing the root's plugin is safe whatever the segment declares.
///
/// Whether before-change notifications are being observed.
+ ///
+ /// Missing parents propagate through intermediate stages so downstream subscriptions detach,
+ /// while the leaf suppresses the default value to match the runtime expression-chain fallback.
+ ///
private static void EmitObservationChainInnerSegments(
StringBuilder sb,
EquatableArray path,
@@ -735,9 +739,25 @@ private static void EmitObservationChainInnerSegments(
var lambdaParam = $"__parent{s}";
var segType = seg.PropertyTypeFullName;
+ // Only the leaf suppresses. Inner segments keep pushing the null downstream so the
+ // stage below re-parents onto null and drops its subscription on the detached subtree.
+ var nullParentBehavior = s == path.Length - 1
+ ? NullParentObservationBehavior.SuppressEmission
+ : NullParentObservationBehavior.EmitDefault;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
+
if (rootPlugin is not null)
{
- rootPlugin.EmitDeepChainInnerSegment(sb, prevVar, curVar, lambdaParam, seg, isBeforeChange);
+ rootPlugin.EmitDeepChainInnerSegment(
+ sb,
+ prevVar,
+ curVar,
+ lambdaParam,
+ seg,
+ isBeforeChange,
+ nullParentBehavior);
}
else if (IsINPChanging(classInfo) && isBeforeChange)
{
@@ -750,7 +770,7 @@ private static void EmitObservationChainInnerSegments(
(global::System.ComponentModel.INotifyPropertyChanging){lambdaParam},
"{seg.PropertyName}",
(global::System.ComponentModel.INotifyPropertyChanging __o) => (({seg.DeclaringTypeFullName})__o).{seg.PropertyName})
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
else
@@ -759,8 +779,10 @@ private static void EmitObservationChainInnerSegments(
.AppendLine($"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
global::ReactiveUI.Binding.Observables.RxBindingExtensions.Select({prevVar},
- {lambdaParam} => (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(
- {lambdaParam} != null ? (({seg.DeclaringTypeFullName}){lambdaParam}).{seg.PropertyName} : default({segType}))));
+ {lambdaParam} => {lambdaParam} != null
+ ? (global::System.IObservable<{segType}>)
+ new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>((({seg.DeclaringTypeFullName}){lambdaParam}).{seg.PropertyName})
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
}
@@ -779,6 +801,10 @@ private static void EmitObservationChainInnerSegments(
///
/// Whether before-change notifications are being observed.
/// The variable-name prefix for the emitted stages.
+ ///
+ /// Missing parents propagate through intermediate stages so downstream subscriptions detach,
+ /// while the leaf suppresses the default value to match the runtime expression-chain fallback.
+ ///
private static void EmitDeepChainInnerSegments(
StringBuilder sb,
EquatableArray path,
@@ -795,9 +821,23 @@ private static void EmitDeepChainInnerSegments(
var lambdaParam = $"{varName}_p{s}";
var segType = seg.PropertyTypeFullName;
+ var nullParentBehavior = s == path.Length - 1
+ ? NullParentObservationBehavior.SuppressEmission
+ : NullParentObservationBehavior.EmitDefault;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
+
if (rootPlugin is not null)
{
- rootPlugin.EmitDeepChainInnerSegment(sb, prevObsVar, curObsVar, lambdaParam, seg, isBeforeChange);
+ rootPlugin.EmitDeepChainInnerSegment(
+ sb,
+ prevObsVar,
+ curObsVar,
+ lambdaParam,
+ seg,
+ isBeforeChange,
+ nullParentBehavior);
}
else if (IsINPChanging(classInfo) && isBeforeChange)
{
@@ -810,7 +850,7 @@ private static void EmitDeepChainInnerSegments(
(global::System.ComponentModel.INotifyPropertyChanging){lambdaParam},
"{seg.PropertyName}",
(global::System.ComponentModel.INotifyPropertyChanging __o) => (({seg.DeclaringTypeFullName})__o).{seg.PropertyName})
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
else
@@ -819,8 +859,10 @@ private static void EmitDeepChainInnerSegments(
.AppendLine($"""
var {curObsVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
global::ReactiveUI.Binding.Observables.RxBindingExtensions.Select({prevObsVar},
- {lambdaParam} => (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(
- {lambdaParam} != null ? (({seg.DeclaringTypeFullName}){lambdaParam}).{seg.PropertyName} : default({segType}))));
+ {lambdaParam} => {lambdaParam} != null
+ ? (global::System.IObservable<{segType}>)
+ new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>((({seg.DeclaringTypeFullName}){lambdaParam}).{seg.PropertyName})
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
}
@@ -836,6 +878,7 @@ private static void EmitDeepChainInnerSegments(
/// The root type's binding info, when known.
/// The observation plugin for the root type, when one matched.
/// The name of the variable the chain result is assigned to.
+ /// Missing parents emit defaults here so binding consumers can clear their targets.
private static void EmitInlineDeepChain(
StringBuilder sb,
string rootVar,
@@ -866,7 +909,14 @@ private static void EmitInlineDeepChain(
if (plugin is not null)
{
- plugin.EmitDeepChainInnerSegment(sb, prevVar, curVar, lambdaParam, seg, false);
+ plugin.EmitDeepChainInnerSegment(
+ sb,
+ prevVar,
+ curVar,
+ lambdaParam,
+ seg,
+ isBeforeChange: false,
+ nullParentBehavior: NullParentObservationBehavior.EmitDefault);
continue;
}
@@ -876,8 +926,10 @@ private static void EmitInlineDeepChain(
.AppendLine($"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
global::ReactiveUI.Binding.Observables.RxBindingExtensions.Select({prevVar},
- {lambdaParam} => (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(
- {lambdaParam} != null ? (({declType}){lambdaParam}).{seg.PropertyName} : default({segType}))));
+ {lambdaParam} => {lambdaParam} != null
+ ? (global::System.IObservable<{segType}>)
+ new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>((({declType}){lambdaParam}).{seg.PropertyName})
+ : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
""");
}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs
index dd587dde..ee1a7c08 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs
@@ -102,13 +102,15 @@ void EmitDeepChainRootSegment(
/// The lambda parameter name for the parent value.
/// The current property path segment.
/// True for WhenChanging (before-change).
+ /// The behavior to use while the parent segment is null.
void EmitDeepChainInnerSegment(
StringBuilder sb,
string prevVar,
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange);
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior);
/// Emits an inline observation variable for binding generators. Used by BindOneWay/BindTwoWay for direct observation code.
/// The string builder to append to.
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/NullParentObservationBehavior.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/NullParentObservationBehavior.cs
new file mode 100644
index 00000000..45619e23
--- /dev/null
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/NullParentObservationBehavior.cs
@@ -0,0 +1,15 @@
+// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.Binding.SourceGenerators.Plugins;
+
+/// Specifies what a generated deep-chain observer emits while its parent segment is null.
+internal enum NullParentObservationBehavior
+{
+ /// Suppresses values until the parent becomes non-null.
+ SuppressEmission,
+
+ /// Emits the leaf type's default value so binding consumers can clear their target.
+ EmitDefault,
+}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs
index 2b96d7e6..32912995 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs
@@ -102,17 +102,23 @@ public void EmitDeepChainInnerSegment(
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange)
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior)
{
var segType = segment.PropertyTypeFullName;
var declType = segment.DeclaringTypeFullName;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
_ = sb.AppendLine()
.AppendLine($"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
global::ReactiveUI.Binding.Observables.RxBindingExtensions.Select({prevVar},
- {lambdaParam} => (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(
- {lambdaParam} != null ? (({declType}){lambdaParam}).{segment.PropertyName} : default({segType}))));
+ {lambdaParam} => {lambdaParam} != null
+ ? (global::System.IObservable<{segType}>)
+ new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>((({declType}){lambdaParam}).{segment.PropertyName})
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs
index 7e460e27..8db441e0 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs
@@ -119,9 +119,13 @@ public void EmitDeepChainInnerSegment(
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange)
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior)
{
var segType = segment.PropertyTypeFullName;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
_ = sb.AppendLine()
.AppendLine(isBeforeChange
@@ -133,7 +137,7 @@ public void EmitDeepChainInnerSegment(
(global::System.ComponentModel.INotifyPropertyChanging){lambdaParam},
"{segment.PropertyName}",
(global::System.ComponentModel.INotifyPropertyChanging __o) => (({segment.DeclaringTypeFullName})__o).{segment.PropertyName})
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
"""
: $"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
@@ -144,7 +148,7 @@ public void EmitDeepChainInnerSegment(
"{segment.PropertyName}",
(global::System.ComponentModel.INotifyPropertyChanged __o) => (({segment.DeclaringTypeFullName})__o).{segment.PropertyName},
false)
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs
index 99434563..cf4cca70 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs
@@ -125,11 +125,15 @@ public void EmitDeepChainInnerSegment(
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange)
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior)
{
var segType = segment.PropertyTypeFullName;
var declType = segment.DeclaringTypeFullName;
var keyPath = ToKvoKeyPath(segment.PropertyName, segment.PropertyTypeFullName);
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
_ = sb.AppendLine()
.AppendLine($"""
@@ -142,7 +146,7 @@ public void EmitDeepChainInnerSegment(
(global::Foundation.NSObject __o) => (({declType})__o).{segment.PropertyName},
false,
{BoolLiteral(isBeforeChange)})
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs
index d7938520..1d14059f 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs
@@ -120,9 +120,13 @@ public void EmitDeepChainInnerSegment(
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange)
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior)
{
var segType = segment.PropertyTypeFullName;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
_ = sb.AppendLine()
.AppendLine(isBeforeChange
@@ -134,7 +138,7 @@ public void EmitDeepChainInnerSegment(
(global::System.ComponentModel.INotifyPropertyChanging){lambdaParam},
"{segment.PropertyName}",
(global::System.ComponentModel.INotifyPropertyChanging __o) => (({segment.DeclaringTypeFullName})__o).{segment.PropertyName})
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
"""
: $"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
@@ -145,7 +149,7 @@ public void EmitDeepChainInnerSegment(
"{segment.PropertyName}",
(global::System.ComponentModel.INotifyPropertyChanged __o) => (({segment.DeclaringTypeFullName})__o).{segment.PropertyName},
false)
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs
index b457edee..f30fcef2 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs
@@ -138,10 +138,14 @@ public void EmitDeepChainInnerSegment(
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange)
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior)
{
var segType = segment.PropertyTypeFullName;
var declType = segment.DeclaringTypeFullName;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
if (isBeforeChange)
{
@@ -149,8 +153,10 @@ public void EmitDeepChainInnerSegment(
.AppendLine($"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
global::ReactiveUI.Binding.Observables.RxBindingExtensions.Select({prevVar},
- {lambdaParam} => (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(
- {lambdaParam} != null ? (({declType}){lambdaParam}).{segment.PropertyName} : default({segType}))));
+ {lambdaParam} => {lambdaParam} != null
+ ? (global::System.IObservable<{segType}>)
+ new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>((({declType}){lambdaParam}).{segment.PropertyName})
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
return;
}
@@ -165,7 +171,7 @@ public void EmitDeepChainInnerSegment(
__h => (({declType}){lambdaParam}).{segment.PropertyName}Changed -= __h,
() => (({declType}){lambdaParam}).{segment.PropertyName},
false)
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs
index 361a4d52..fe4a59c9 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs
@@ -133,10 +133,14 @@ public void EmitDeepChainInnerSegment(
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange)
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior)
{
var segType = segment.PropertyTypeFullName;
var declType = segment.DeclaringTypeFullName;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
if (isBeforeChange)
{
@@ -144,8 +148,10 @@ public void EmitDeepChainInnerSegment(
.AppendLine($"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
global::ReactiveUI.Binding.Observables.RxBindingExtensions.Select({prevVar},
- {lambdaParam} => (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(
- {lambdaParam} != null ? (({declType}){lambdaParam}).{segment.PropertyName} : default({segType}))));
+ {lambdaParam} => {lambdaParam} != null
+ ? (global::System.IObservable<{segType}>)
+ new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>((({declType}){lambdaParam}).{segment.PropertyName})
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
return;
}
@@ -160,7 +166,7 @@ public void EmitDeepChainInnerSegment(
{declType}.{segment.PropertyName}Property,
(global::Microsoft.UI.Xaml.DependencyObject __o) => (({declType})__o).{segment.PropertyName},
false)
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
diff --git a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs
index 72699d0e..6db05b72 100644
--- a/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs
+++ b/src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs
@@ -146,10 +146,14 @@ public void EmitDeepChainInnerSegment(
string curVar,
string lambdaParam,
PropertyPathSegment segment,
- bool isBeforeChange)
+ bool isBeforeChange,
+ NullParentObservationBehavior nullParentBehavior)
{
var segType = segment.PropertyTypeFullName;
var declType = segment.DeclaringTypeFullName;
+ var nullParentObservable = nullParentBehavior == NullParentObservationBehavior.EmitDefault
+ ? $"new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))"
+ : $"global::ReactiveUI.Binding.Observables.EmptyObservable<{segType}>.Instance";
if (isBeforeChange)
{
@@ -158,8 +162,10 @@ public void EmitDeepChainInnerSegment(
.AppendLine($"""
var {curVar} = global::ReactiveUI.Binding.Observables.RxBindingExtensions.Switch(
global::ReactiveUI.Binding.Observables.RxBindingExtensions.Select({prevVar},
- {lambdaParam} => (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(
- {lambdaParam} != null ? (({declType}){lambdaParam}).{segment.PropertyName} : default({segType}))));
+ {lambdaParam} => {lambdaParam} != null
+ ? (global::System.IObservable<{segType}>)
+ new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>((({declType}){lambdaParam}).{segment.PropertyName})
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
return;
}
@@ -176,7 +182,7 @@ public void EmitDeepChainInnerSegment(
{declType}.{segment.PropertyName}Property, typeof({declType})).RemoveValueChanged({lambdaParam}, __h),
() => (({declType}){lambdaParam}).{segment.PropertyName},
false)
- : (global::System.IObservable<{segType}>)new global::ReactiveUI.Binding.Observables.ReturnObservable<{segType}>(default({segType}))));
+ : (global::System.IObservable<{segType}>){nullParentObservable}));
""");
}
diff --git a/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/Scenarios/WhenAnyValueScenarios.cs b/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/Scenarios/WhenAnyValueScenarios.cs
index 68fe8440..f2f66bce 100644
--- a/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/Scenarios/WhenAnyValueScenarios.cs
+++ b/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/Scenarios/WhenAnyValueScenarios.cs
@@ -43,4 +43,25 @@ public static IObservable WithSelector_TwoProperties(WhenAnyTestFixture
/// An observable of the nested City property value.
public static IObservable DeepChain_AddressCity(BigViewModel vm) =>
vm.WhenAnyValue(x => x.Address.City);
+
+ ///
+ /// Deep property chain observation on HostTestFixture.Child!.Name.
+ /// Uses the null-forgiving operator to test generator support for nullable intermediates.
+ ///
+ /// The host fixture to observe.
+ /// An observable of the nested Name property value.
+ public static IObservable DeepChain_ChildName(HostTestFixture host) =>
+ host.WhenAnyValue(x => x.Child!.Name);
+
+ /// Three-link property chain observation on HostTestFixture.Child!.Child!.Name.
+ /// The host fixture to observe.
+ /// An observable of the nested grandchild Name property value.
+ public static IObservable DeepChain_GrandchildName(HostTestFixture host) =>
+ host.WhenAnyValue(x => x.Child!.Child!.Name);
+
+ /// Deep property chain observation on HostTestFixture.Child!.Age.
+ /// The host fixture to observe.
+ /// An observable of the nested Age property value.
+ public static IObservable DeepChain_ChildAge(HostTestFixture host) =>
+ host.WhenAnyValue(x => x.Child!.Age);
}
diff --git a/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/TestModels/TestViewModel.cs b/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/TestModels/TestViewModel.cs
index e95d4263..51c132b4 100644
--- a/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/TestModels/TestViewModel.cs
+++ b/src/tests/ReactiveUI.Binding.GeneratedCode.TestModels/TestModels/TestViewModel.cs
@@ -51,4 +51,21 @@ public int Age
PropertyChanged?.Invoke(this, new(nameof(Age)));
}
}
+
+ /// Gets or sets the nested child used by three-link observation tests.
+ public TestViewModel? Child
+ {
+ get => field;
+ set
+ {
+ if (field == value)
+ {
+ return;
+ }
+
+ PropertyChanging?.Invoke(this, new(nameof(Child)));
+ field = value;
+ PropertyChanged?.Invoke(this, new(nameof(Child)));
+ }
+ }
}
diff --git a/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenAnyValue/WhenAnyValueEdgeCaseTests.cs b/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenAnyValue/WhenAnyValueEdgeCaseTests.cs
index ac0cfa17..8b419874 100644
--- a/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenAnyValue/WhenAnyValueEdgeCaseTests.cs
+++ b/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenAnyValue/WhenAnyValueEdgeCaseTests.cs
@@ -2,6 +2,7 @@
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
+using ReactiveUI.Binding.Fallback;
using ReactiveUI.Binding.GeneratedCode.TestModels.Scenarios;
using ReactiveUI.Binding.GeneratedCode.TestModels.TestModels;
@@ -34,6 +35,12 @@ public class WhenAnyValueEdgeCaseTests
/// The value written to a property to trigger a change notification.
private const string ChangedValue = "Changed";
+ /// The initial child name used in nullable deep-chain tests.
+ private const string Alice = "Alice";
+
+ /// The replacement child name used in nullable deep-chain tests.
+ private const string Charlie = "Charlie";
+
/// Verifies that disposing the WhenAnyValue subscription stops listening for changes.
/// A task representing the asynchronous test operation.
[Test]
@@ -134,6 +141,114 @@ public async Task DeepChain_IntermediateObjectReplacement()
await Assert.That(values).Contains("Eugene");
}
+ ///
+ /// Verifies that a nullable deep chain preserves legacy ReactiveUI WhenAnyValue semantics.
+ /// A missing intermediate suppresses emission, and replacing it reattaches the observation.
+ ///
+ /// A task representing the asynchronous test operation.
+ [Test]
+ public async Task DeepChain_NullIntermediate_MatchesLegacyReactiveUISemantics()
+ {
+ var generatedHost = new HostTestFixture { Child = null };
+ var fallbackHost = new HostTestFixture { Child = null };
+ var generatedValues = new List();
+ var fallbackValues = new List();
+
+ using var generatedSub = WhenAnyValueScenarios.DeepChain_ChildName(generatedHost)
+ .Subscribe(generatedValues.Add);
+ using var fallbackSub = RuntimeObservationFallback
+ .WhenAnyValue(fallbackHost, x => x.Child!.Name)
+ .Subscribe(fallbackValues.Add);
+
+ generatedHost.Child = new() { Name = Alice };
+ fallbackHost.Child = new() { Name = Alice };
+ generatedHost.Child.Name = "Bob";
+ fallbackHost.Child.Name = "Bob";
+
+ generatedHost.Child = null;
+ fallbackHost.Child = null;
+
+ generatedHost.Child = new() { Name = Charlie };
+ fallbackHost.Child = new() { Name = Charlie };
+
+ var generatedTrace = string.Join('|', generatedValues);
+ var fallbackTrace = string.Join('|', fallbackValues);
+ await Assert.That(generatedTrace).IsEqualTo(fallbackTrace);
+ await Assert.That(generatedTrace).IsEqualTo("Alice|Bob|Charlie");
+ }
+
+ ///
+ /// Verifies that breaking a three-link chain detaches the generated and legacy observers from
+ /// the orphaned subtree, while repairing the chain reattaches both implementations.
+ ///
+ /// A task representing the asynchronous test operation.
+ [Test]
+ public async Task DeepChain_ThreeLinks_DetachesOrphanedSubtreeLikeLegacyReactiveUI()
+ {
+ var generatedMiddle = new TestViewModel { Child = new() { Name = Alice } };
+ var fallbackMiddle = new TestViewModel { Child = new() { Name = Alice } };
+ var generatedHost = new HostTestFixture { Child = generatedMiddle };
+ var fallbackHost = new HostTestFixture { Child = fallbackMiddle };
+ var generatedValues = new List();
+ var fallbackValues = new List();
+
+ using var generatedSub = WhenAnyValueScenarios.DeepChain_GrandchildName(generatedHost)
+ .Subscribe(generatedValues.Add);
+ using var fallbackSub = RuntimeObservationFallback
+ .WhenAnyValue(fallbackHost, x => x.Child!.Child!.Name)
+ .Subscribe(fallbackValues.Add);
+
+ generatedHost.Child = null;
+ fallbackHost.Child = null;
+
+ generatedMiddle.Child!.Name = "Orphan mutation";
+ fallbackMiddle.Child!.Name = "Orphan mutation";
+
+ generatedHost.Child = new() { Child = new() { Name = Charlie } };
+ fallbackHost.Child = new() { Child = new() { Name = Charlie } };
+
+ var generatedTrace = string.Join('|', generatedValues);
+ var fallbackTrace = string.Join('|', fallbackValues);
+ await Assert.That(generatedTrace).IsEqualTo(fallbackTrace);
+ await Assert.That(generatedTrace).IsEqualTo("Alice|Charlie");
+ }
+
+ /// Verifies that a null leaf is still emitted when every intermediate object exists.
+ /// A task representing the asynchronous test operation.
+ [Test]
+ public async Task DeepChain_NullLeaf_StillEmits()
+ {
+ var host = new HostTestFixture { Child = new() { Name = Alice } };
+ var values = new List();
+
+ using var sub = WhenAnyValueScenarios.DeepChain_ChildName(host)
+ .Subscribe(values.Add);
+
+ host.Child.Name = null!;
+
+ await Assert.That(values.Count).IsEqualTo(ExpectedEmissionCount);
+ await Assert.That(values[1]).IsNull();
+ }
+
+ /// Verifies that a missing intermediate is distinct from a legitimate default-valued leaf.
+ /// A task representing the asynchronous test operation.
+ [Test]
+ public async Task DeepChain_DefaultValueLeaf_EmitsOnlyWhenIntermediateExists()
+ {
+ var host = new HostTestFixture { Child = null };
+ var values = new List();
+
+ using var sub = WhenAnyValueScenarios.DeepChain_ChildAge(host)
+ .Subscribe(values.Add);
+
+ await Assert.That(values.Count).IsEqualTo(0);
+
+ host.Child = new() { Age = 0 };
+
+ await Assert.That(values.Count).IsEqualTo(1);
+ await Assert.That(values[0]).IsEqualTo(0);
+ }
+
/// Verifies that WhenAnyValue with selector re-emits when properties change.
/// A task representing the asynchronous test operation.
[Test]
diff --git a/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenChanged/WhenChangedEdgeCaseTests.cs b/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenChanged/WhenChangedEdgeCaseTests.cs
index 1a13bff0..9bfeb7d1 100644
--- a/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenChanged/WhenChangedEdgeCaseTests.cs
+++ b/src/tests/ReactiveUI.Binding.GeneratedCode.Tests/WhenChanged/WhenChangedEdgeCaseTests.cs
@@ -331,10 +331,10 @@ public async Task DeepChain_NullForgiving_EmitsWhenChildSet()
await Assert.That(values).Contains("Bob");
}
- /// Verifies that deep chain with null-forgiving operator emits default when the intermediate object is null.
+ /// Verifies that deep chain with null-forgiving operator does not emit when the intermediate object is null.
/// A task representing the asynchronous test operation.
[Test]
- public async Task DeepChain_NullForgiving_EmitsDefaultWhenChildNull()
+ public async Task DeepChain_NullForgiving_DoesNotEmitWhenChildNull()
{
var host = new HostTestFixture { Child = null };
var values = new List();
@@ -342,8 +342,7 @@ public async Task DeepChain_NullForgiving_EmitsDefaultWhenChildNull()
using var sub = WhenChangedScenarios.DeepChain_ChildName(host)
.Subscribe(values.Add);
- // When Child is null, should emit default (null for string)
- await Assert.That(values.Count).IsGreaterThanOrEqualTo(1);
+ await Assert.That(values.Count).IsEqualTo(0);
}
/// Verifies that deep chain with null-forgiving operator re-subscribes when the intermediate object is replaced.
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs
index 713700c9..6f19d54b 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs
@@ -177,7 +177,7 @@ public async Task WpfPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsEventObse
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission);
var result = sb.ToString();
await Assert.That(result).Contains(EventObservableName);
@@ -193,7 +193,7 @@ public async Task WpfPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsReturnOb
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission);
await Assert.That(sb.ToString()).Contains(ReturnObservableName);
}
@@ -336,7 +336,7 @@ public async Task WinFormsPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsEven
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission);
var result = sb.ToString();
await Assert.That(result).Contains(EventObservableName);
@@ -352,7 +352,7 @@ public async Task WinFormsPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsRet
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission);
await Assert.That(sb.ToString()).Contains(ReturnObservableName);
}
@@ -468,7 +468,7 @@ public async Task WinUIPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsWinUIDP
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission);
var result = sb.ToString();
await Assert.That(result).Contains(WinUIDPObservableLocal);
@@ -484,7 +484,7 @@ public async Task WinUIPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsReturn
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("Text", StringName, InnerTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission);
await Assert.That(sb.ToString()).Contains(ReturnObservableName);
}
@@ -594,7 +594,7 @@ public async Task KVOPlugin_EmitDeepChainInnerSegment_AfterChange_EmitsKVOObserv
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission);
var result = sb.ToString();
await Assert.That(result).Contains(KVOObservableLocal);
@@ -610,7 +610,7 @@ public async Task KVOPlugin_EmitDeepChainInnerSegment_BeforeChange_EmitsKVOObser
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, true, NullParentObservationBehavior.SuppressEmission);
var result = sb.ToString();
await Assert.That(result).Contains(KVOObservableLocal);
@@ -749,7 +749,7 @@ public async Task AndroidPlugin_EmitDeepChainInnerSegment_EmitsReturnObservable(
var sb = new StringBuilder();
var segment = ModelFactory.CreatePropertyPathSegment("City", StringName, AddressTypeName);
- plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false);
+ plugin.EmitDeepChainInnerSegment(sb, Obs0Local, Obs1Local, "__p1", segment, false, NullParentObservationBehavior.SuppressEmission);
var result = sb.ToString();
await Assert.That(result).Contains(ReturnObservableName);
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs
index c48ebdfe..759d8c66 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.MP_DC#WhenAnyDispatch.g.verified.cs
@@ -48,7 +48,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Name",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.MultiPropertyDeepChain.ChildModel)__o).Name,
false)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
var __propObs0 = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__propObs0_s1);
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs
index 00b55946..882a6b9f 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAG.SP_DC#WhenAnyDispatch.g.verified.cs
@@ -45,7 +45,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Name",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAny.DeepPropertyChain.ChildModel)__o).Name,
false)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
var __propObs0 = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__propObs0_s1);
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs
index f3317f3f..46799d8c 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_CL#WhenAnyObservableDispatch.g.verified.cs
@@ -48,7 +48,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Count",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Count,
false)
- : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.ReturnObservable>(default(global::System.IObservable))));
+ : (global::System.IObservable>)global::ReactiveUI.Binding.Observables.EmptyObservable>.Instance));
var __obsProperty0 = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obsProperty0_s1);
@@ -70,7 +70,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Message",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableCombineLatest.ChildModel)__o).Message,
false)
- : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.ReturnObservable>(default(global::System.IObservable))));
+ : (global::System.IObservable>)global::ReactiveUI.Binding.Observables.EmptyObservable>.Instance));
var __obsProperty1 = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obsProperty1_s1);
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs
index ebc75b82..2f32a360 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.2O_DC_Merge#WhenAnyObservableDispatch.g.verified.cs
@@ -47,7 +47,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Command1",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command1,
false)
- : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.ReturnObservable>(default(global::System.IObservable))));
+ : (global::System.IObservable>)global::ReactiveUI.Binding.Observables.EmptyObservable>.Instance));
var __obsProperty0 = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obsProperty0_s1);
@@ -69,7 +69,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Command2",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableMerge.ChildModel)__o).Command2,
false)
- : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.ReturnObservable>(default(global::System.IObservable))));
+ : (global::System.IObservable>)global::ReactiveUI.Binding.Observables.EmptyObservable>.Instance));
var __obsProperty1 = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obsProperty1_s1);
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs
index 284ea224..84c89866 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAOG.SingleObservable_DC#WhenAnyObservableDispatch.g.verified.cs
@@ -44,7 +44,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"MyCommand",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyObservable.DeepObservableSwitch.ChildModel)__o).MyCommand,
false)
- : (global::System.IObservable>)new global::ReactiveUI.Binding.Observables.ReturnObservable>(default(global::System.IObservable))));
+ : (global::System.IObservable>)global::ReactiveUI.Binding.Observables.EmptyObservable>.Instance));
var __obsProperty = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obsProperty_s1);
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs
index 054f290d..359c2727 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.DPC#WhenAnyValueDispatch.g.verified.cs
@@ -50,7 +50,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Name",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenAnyValue.DeepPropertyChain.ChildModel)__o).Name,
false)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obs1);
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs
index c338336d..4f9c940f 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.4LDC#WhenChangedDispatch.g.verified.cs
@@ -70,7 +70,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Value",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.FourLevelDeepChain.Model)__o).Value,
false)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obs3);
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs
index 23d3d47d..be09522f 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.DPC#WhenChangedDispatch.g.verified.cs
@@ -50,7 +50,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Name",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.DeepPropertyChain.ChildModel)__o).Name,
false)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obs1);
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs
index 45ce9aef..c01547d5 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MP_WDC#WhenChangedDispatch.g.verified.cs
@@ -56,7 +56,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"City",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.MultiPropertyWithDeepChains.AddressModel)__o).City,
false)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
var __propObs0 = global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__propObs0_s1);
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs
index 5aa1a78d..a768c7d2 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullForgivingDC#WhenChangedDispatch.g.verified.cs
@@ -50,7 +50,7 @@ internal static partial class __ReactiveUIGeneratedBindings
"Name",
(global::System.ComponentModel.INotifyPropertyChanged __o) => ((global::SharedScenarios.WhenChanged.NullForgivingDeepChain.ChildModel)__o).Name,
false)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return global::ReactiveUI.Binding.Observables.RxBindingExtensions.DistinctUntilChanged(__obs1);
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs
index 163649ba..009c09d3 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC#WhenChangingDispatch.g.verified.cs
@@ -66,7 +66,7 @@ internal static partial class __ReactiveUIGeneratedBindings
(global::System.ComponentModel.INotifyPropertyChanging)__parent3,
"Value",
(global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return __obs3;
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs
index 44aa7091..81681049 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.4LDC_CFP#WhenChangingDispatch.g.verified.cs
@@ -64,7 +64,7 @@ internal static partial class __ReactiveUIGeneratedBindings
(global::System.ComponentModel.INotifyPropertyChanging)__parent3,
"Value",
(global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.FourLevelDeepChain.Model)__o).Value)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return __obs3;
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs
index c9a98d60..e7c105f1 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC#WhenChangingDispatch.g.verified.cs
@@ -48,7 +48,7 @@ internal static partial class __ReactiveUIGeneratedBindings
(global::System.ComponentModel.INotifyPropertyChanging)__parent1,
"Name",
(global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return __obs1;
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs
index 2ca1f9bf..c85826cb 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.DPC_CFP#WhenChangingDispatch.g.verified.cs
@@ -46,7 +46,7 @@ internal static partial class __ReactiveUIGeneratedBindings
(global::System.ComponentModel.INotifyPropertyChanging)__parent1,
"Name",
(global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.DeepPropertyChain.ChildModel)__o).Name)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
return __obs1;
}
diff --git a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs
index 2b60b8d8..fe6b206c 100644
--- a/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs
+++ b/src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.MP_WDC#WhenChangingDispatch.g.verified.cs
@@ -54,7 +54,7 @@ internal static partial class __ReactiveUIGeneratedBindings
(global::System.ComponentModel.INotifyPropertyChanging)__propObs0_p1,
"City",
(global::System.ComponentModel.INotifyPropertyChanging __o) => ((global::SharedScenarios.WhenChanging.MultiPropertyWithDeepChains.AddressModel)__o).City)
- : (global::System.IObservable)new global::ReactiveUI.Binding.Observables.ReturnObservable(default(string))));
+ : (global::System.IObservable)global::ReactiveUI.Binding.Observables.EmptyObservable.Instance));
var __propObs0 = __propObs0_s1;