ExpressionObserverTests_Property.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reactive;
  4. using System.Reactive.Linq;
  5. using System.Reactive.Subjects;
  6. using System.Threading.Tasks;
  7. using Avalonia.Data;
  8. using Avalonia.Data.Core;
  9. using Avalonia.Threading;
  10. using Avalonia.UnitTests;
  11. using Microsoft.Reactive.Testing;
  12. using Xunit;
  13. namespace Avalonia.Base.UnitTests.Data.Core
  14. {
  15. public class ExpressionObserverTests_Property
  16. {
  17. [Fact]
  18. public async Task Should_Get_Simple_Property_Value()
  19. {
  20. var data = new { Foo = "foo" };
  21. var target = ExpressionObserver.Create(data, o => o.Foo);
  22. var result = await target.Take(1);
  23. Assert.Equal("foo", result);
  24. GC.KeepAlive(data);
  25. }
  26. [Fact]
  27. public void Should_Get_Simple_Property_Value_Type()
  28. {
  29. var data = new { Foo = "foo" };
  30. var target = ExpressionObserver.Create(data, o => o.Foo);
  31. target.Subscribe(_ => { });
  32. Assert.Equal(typeof(string), target.ResultType);
  33. GC.KeepAlive(data);
  34. }
  35. [Fact]
  36. public async Task Should_Get_Simple_Property_Value_Null()
  37. {
  38. var data = new { Foo = (string)null };
  39. var target = ExpressionObserver.Create(data, o => o.Foo);
  40. var result = await target.Take(1);
  41. Assert.Null(result);
  42. GC.KeepAlive(data);
  43. }
  44. [Fact]
  45. public async Task Should_Get_Simple_Property_From_Base_Class()
  46. {
  47. var data = new Class3 { Foo = "foo" };
  48. var target = ExpressionObserver.Create(data, o => o.Foo);
  49. var result = await target.Take(1);
  50. Assert.Equal("foo", result);
  51. GC.KeepAlive(data);
  52. }
  53. [Fact]
  54. public async Task Should_Return_BindingNotification_Error_For_Root_Null()
  55. {
  56. var target = ExpressionObserver.Create(default(Class3), o => o.Foo);
  57. var result = await target.Take(1);
  58. Assert.Equal(
  59. new BindingNotification(
  60. new MarkupBindingChainException("Null value", "o => o.Foo", string.Empty),
  61. BindingErrorType.Error,
  62. AvaloniaProperty.UnsetValue),
  63. result);
  64. }
  65. [Fact]
  66. public async Task Should_Return_BindingNotification_Error_For_Root_UnsetValue()
  67. {
  68. var target = ExpressionObserver.Create(AvaloniaProperty.UnsetValue, o => (o as Class3).Foo);
  69. var result = await target.Take(1);
  70. Assert.Equal(
  71. new BindingNotification(
  72. new MarkupBindingChainException("Null value", "o => (o As Class3).Foo", string.Empty),
  73. BindingErrorType.Error,
  74. AvaloniaProperty.UnsetValue),
  75. result);
  76. }
  77. [Fact]
  78. public async Task Should_Return_BindingNotification_Error_For_Observable_Root_Null()
  79. {
  80. var target = ExpressionObserver.Create(Observable.Return(default(Class3)), o => o.Foo);
  81. var result = await target.Take(1);
  82. Assert.Equal(
  83. new BindingNotification(
  84. new MarkupBindingChainException("Null value", "o => o.Foo", string.Empty),
  85. BindingErrorType.Error,
  86. AvaloniaProperty.UnsetValue),
  87. result);
  88. }
  89. [Fact]
  90. public async void Should_Return_BindingNotification_Error_For_Observable_Root_UnsetValue()
  91. {
  92. var target = ExpressionObserver.Create<object, string>(Observable.Return(AvaloniaProperty.UnsetValue), o => (o as Class3).Foo);
  93. var result = await target.Take(1);
  94. Assert.Equal(
  95. new BindingNotification(
  96. new MarkupBindingChainException("Null value", "o => (o As Class3).Foo", string.Empty),
  97. BindingErrorType.Error,
  98. AvaloniaProperty.UnsetValue),
  99. result);
  100. }
  101. [Fact]
  102. public async Task Should_Get_Simple_Property_Chain()
  103. {
  104. var data = new { Foo = new { Bar = new { Baz = "baz" } } };
  105. var target = ExpressionObserver.Create(data, o => o.Foo.Bar.Baz);
  106. var result = await target.Take(1);
  107. Assert.Equal("baz", result);
  108. GC.KeepAlive(data);
  109. }
  110. [Fact]
  111. public void Should_Get_Simple_Property_Chain_Type()
  112. {
  113. var data = new { Foo = new { Bar = new { Baz = "baz" } } };
  114. var target = ExpressionObserver.Create(data, o => o.Foo.Bar.Baz);
  115. target.Subscribe(_ => { });
  116. Assert.Equal(typeof(string), target.ResultType);
  117. GC.KeepAlive(data);
  118. }
  119. [Fact]
  120. public void Should_Return_BindingNotification_Error_For_Chain_With_Null_Value()
  121. {
  122. var data = new { Foo = default(Class1) };
  123. var target = ExpressionObserver.Create(data, o => o.Foo.Foo.Length);
  124. var result = new List<object>();
  125. target.Subscribe(x => result.Add(x));
  126. Assert.Equal(
  127. new[]
  128. {
  129. new BindingNotification(
  130. new MarkupBindingChainException("Null value", "o => o.Foo.Foo.Length", "Foo"),
  131. BindingErrorType.Error,
  132. AvaloniaProperty.UnsetValue),
  133. },
  134. result);
  135. GC.KeepAlive(data);
  136. }
  137. [Fact]
  138. public void Should_Track_Simple_Property_Value()
  139. {
  140. var data = new Class1 { Foo = "foo" };
  141. var target = ExpressionObserver.Create(data, o => o.Foo);
  142. var result = new List<object>();
  143. var sub = target.Subscribe(x => result.Add(x));
  144. data.Foo = "bar";
  145. Assert.Equal(new[] { "foo", "bar" }, result);
  146. sub.Dispose();
  147. // Forces WeakEvent compact
  148. Dispatcher.UIThread.RunJobs();
  149. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  150. GC.KeepAlive(data);
  151. }
  152. [Fact]
  153. public void Should_Trigger_PropertyChanged_On_Null_Or_Empty_String()
  154. {
  155. var data = new Class1 { Bar = "foo" };
  156. var target = ExpressionObserver.Create(data, o => o.Bar);
  157. var result = new List<object>();
  158. var sub = target.Subscribe(x => result.Add(x));
  159. Assert.Equal(new[] { "foo" }, result);
  160. data.Bar = "bar";
  161. Assert.Equal(new[] { "foo" }, result);
  162. data.RaisePropertyChanged(string.Empty);
  163. Assert.Equal(new[] { "foo", "bar" }, result);
  164. data.RaisePropertyChanged(null);
  165. Assert.Equal(new[] { "foo", "bar", "bar" }, result);
  166. sub.Dispose();
  167. // Forces WeakEvent compact
  168. Dispatcher.UIThread.RunJobs();
  169. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  170. GC.KeepAlive(data);
  171. }
  172. [Fact]
  173. public void Should_Track_End_Of_Property_Chain_Changing()
  174. {
  175. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  176. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  177. var result = new List<object>();
  178. var sub = target.Subscribe(x => result.Add(x));
  179. ((Class2)data.Next).Bar = "baz";
  180. ((Class2)data.Next).Bar = null;
  181. Assert.Equal(new[] { "bar", "baz", null }, result);
  182. sub.Dispose();
  183. // Forces WeakEvent compact
  184. Dispatcher.UIThread.RunJobs();
  185. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  186. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  187. GC.KeepAlive(data);
  188. }
  189. [Fact]
  190. public void Should_Track_Property_Chain_Changing()
  191. {
  192. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  193. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  194. var result = new List<object>();
  195. var sub = target.Subscribe(x => result.Add(x));
  196. var old = data.Next;
  197. data.Next = new Class2 { Bar = "baz" };
  198. data.Next = new Class2 { Bar = null };
  199. Assert.Equal(new[] { "bar", "baz", null }, result);
  200. sub.Dispose();
  201. // Forces WeakEvent compact
  202. Dispatcher.UIThread.RunJobs();
  203. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  204. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  205. Assert.Equal(0, old.PropertyChangedSubscriptionCount);
  206. GC.KeepAlive(data);
  207. }
  208. [Fact]
  209. public void Should_Track_Property_Chain_Breaking_With_Null_Then_Mending()
  210. {
  211. var data = new Class1
  212. {
  213. Next = new Class2
  214. {
  215. Next = new Class2
  216. {
  217. Bar = "bar"
  218. }
  219. }
  220. };
  221. var target = ExpressionObserver.Create(data, o => ((o.Next as Class2).Next as Class2).Bar);
  222. var result = new List<object>();
  223. var sub = target.Subscribe(x => result.Add(x));
  224. var old = data.Next;
  225. data.Next = new Class2 { Bar = "baz" };
  226. data.Next = old;
  227. Assert.Equal(
  228. new object[]
  229. {
  230. "bar",
  231. new BindingNotification(
  232. new MarkupBindingChainException("Null value", "o => ((o.Next As Class2).Next As Class2).Bar", "Next.Next"),
  233. BindingErrorType.Error,
  234. AvaloniaProperty.UnsetValue),
  235. "bar"
  236. },
  237. result);
  238. sub.Dispose();
  239. // Forces WeakEvent compact
  240. Dispatcher.UIThread.RunJobs();
  241. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  242. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  243. Assert.Equal(0, old.PropertyChangedSubscriptionCount);
  244. GC.KeepAlive(data);
  245. }
  246. [Fact]
  247. public void Should_Track_Property_Chain_Breaking_With_Missing_Member_Then_Mending()
  248. {
  249. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  250. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  251. var result = new List<object>();
  252. var sub = target.Subscribe(x => result.Add(x));
  253. var old = data.Next;
  254. var breaking = new WithoutBar();
  255. data.Next = breaking;
  256. data.Next = new Class2 { Bar = "baz" };
  257. Assert.Equal(
  258. new object[]
  259. {
  260. "bar",
  261. new BindingNotification(
  262. new MissingMemberException("Could not find a matching property accessor for 'Bar' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+WithoutBar'"),
  263. BindingErrorType.Error),
  264. "baz",
  265. },
  266. result);
  267. sub.Dispose();
  268. // Forces WeakEvent compact
  269. Dispatcher.UIThread.RunJobs();
  270. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  271. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  272. Assert.Equal(0, breaking.PropertyChangedSubscriptionCount);
  273. Assert.Equal(0, old.PropertyChangedSubscriptionCount);
  274. GC.KeepAlive(data);
  275. }
  276. [Fact]
  277. public void Empty_Expression_Should_Track_Root()
  278. {
  279. var data = new Class1 { Foo = "foo" };
  280. var update = new Subject<ValueTuple>();
  281. var target = ExpressionObserver.Create(() => data.Foo, o => o, update);
  282. var result = new List<object>();
  283. target.Subscribe(x => result.Add(x));
  284. data.Foo = "bar";
  285. update.OnNext(default);
  286. Assert.Equal(new[] { "foo", "bar" }, result);
  287. GC.KeepAlive(data);
  288. }
  289. [Fact]
  290. public void Should_Track_Property_Value_From_Observable_Root()
  291. {
  292. var scheduler = new TestScheduler();
  293. var source = scheduler.CreateColdObservable(
  294. OnNext(1, new Class1 { Foo = "foo" }),
  295. OnNext(2, new Class1 { Foo = "bar" }));
  296. var target = ExpressionObserver.Create(source, o => o.Foo);
  297. var result = new List<object>();
  298. using (target.Subscribe(x => result.Add(x)))
  299. {
  300. scheduler.Start();
  301. }
  302. Assert.Equal(new[] { "foo", "bar" }, result);
  303. Assert.All(source.Subscriptions, x => Assert.NotEqual(Subscription.Infinite, x.Unsubscribe));
  304. }
  305. [Fact]
  306. public void Subscribing_Multiple_Times_Should_Return_Values_To_All()
  307. {
  308. var data = new Class1 { Foo = "foo" };
  309. var target = ExpressionObserver.Create(data, o => o.Foo);
  310. var result1 = new List<object>();
  311. var result2 = new List<object>();
  312. var result3 = new List<object>();
  313. target.Subscribe(x => result1.Add(x));
  314. target.Subscribe(x => result2.Add(x));
  315. data.Foo = "bar";
  316. target.Subscribe(x => result3.Add(x));
  317. Assert.Equal(new[] { "foo", "bar" }, result1);
  318. Assert.Equal(new[] { "foo", "bar" }, result2);
  319. Assert.Equal(new[] { "bar" }, result3);
  320. GC.KeepAlive(data);
  321. }
  322. [Fact]
  323. public void Subscribing_Multiple_Times_Should_Only_Add_PropertyChanged_Handlers_Once()
  324. {
  325. var data = new Class1 { Foo = "foo" };
  326. var target = ExpressionObserver.Create(data, o => o.Foo);
  327. var sub1 = target.Subscribe(x => { });
  328. var sub2 = target.Subscribe(x => { });
  329. Assert.Equal(1, data.PropertyChangedSubscriptionCount);
  330. sub1.Dispose();
  331. sub2.Dispose();
  332. // Forces WeakEvent compact
  333. Dispatcher.UIThread.RunJobs();
  334. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  335. GC.KeepAlive(data);
  336. }
  337. [Fact]
  338. public void SetValue_Should_Set_Simple_Property_Value()
  339. {
  340. var data = new Class1 { Foo = "foo" };
  341. var target = ExpressionObserver.Create(data, o => o.Foo);
  342. using (target.Subscribe(_ => { }))
  343. {
  344. Assert.True(target.SetValue("bar"));
  345. }
  346. Assert.Equal("bar", data.Foo);
  347. GC.KeepAlive(data);
  348. }
  349. [Fact]
  350. public void SetValue_Should_Set_Property_At_The_End_Of_Chain()
  351. {
  352. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  353. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  354. using (target.Subscribe(_ => { }))
  355. {
  356. Assert.True(target.SetValue("baz"));
  357. }
  358. Assert.Equal("baz", ((Class2)data.Next).Bar);
  359. GC.KeepAlive(data);
  360. }
  361. [Fact]
  362. public void SetValue_Should_Return_False_For_Missing_Property()
  363. {
  364. var data = new Class1 { Next = new WithoutBar() };
  365. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  366. using (target.Subscribe(_ => { }))
  367. {
  368. Assert.False(target.SetValue("baz"));
  369. }
  370. GC.KeepAlive(data);
  371. }
  372. [Fact]
  373. public void SetValue_Should_Notify_New_Value_With_Inpc()
  374. {
  375. var data = new Class1();
  376. var target = ExpressionObserver.Create(data, o => o.Foo);
  377. var result = new List<object>();
  378. target.Subscribe(x => result.Add(x));
  379. target.SetValue("bar");
  380. Assert.Equal(new[] { null, "bar" }, result);
  381. GC.KeepAlive(data);
  382. }
  383. [Fact]
  384. public void SetValue_Should_Notify_New_Value_Without_Inpc()
  385. {
  386. var data = new Class1();
  387. var target = ExpressionObserver.Create(data, o => o.Bar);
  388. var result = new List<object>();
  389. target.Subscribe(x => result.Add(x));
  390. target.SetValue("bar");
  391. Assert.Equal(new[] { null, "bar" }, result);
  392. GC.KeepAlive(data);
  393. }
  394. [Fact]
  395. public void SetValue_Should_Return_False_For_Missing_Object()
  396. {
  397. var data = new Class1();
  398. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  399. using (target.Subscribe(_ => { }))
  400. {
  401. Assert.False(target.SetValue("baz"));
  402. }
  403. GC.KeepAlive(data);
  404. }
  405. [Fact]
  406. public void Can_Replace_Root()
  407. {
  408. var first = new Class1 { Foo = "foo" };
  409. var second = new Class1 { Foo = "bar" };
  410. var root = first;
  411. var update = new Subject<ValueTuple>();
  412. var target = ExpressionObserver.Create(() => root, o => o.Foo, update);
  413. var result = new List<object>();
  414. var sub = target.Subscribe(x => result.Add(x));
  415. root = second;
  416. update.OnNext(default);
  417. root = null;
  418. update.OnNext(default);
  419. Assert.Equal(
  420. new object[]
  421. {
  422. "foo",
  423. "bar",
  424. new BindingNotification(
  425. new MarkupBindingChainException("Null value", "o => o.Foo", string.Empty),
  426. BindingErrorType.Error,
  427. AvaloniaProperty.UnsetValue)
  428. },
  429. result);
  430. // Forces WeakEvent compact
  431. Dispatcher.UIThread.RunJobs();
  432. Assert.Equal(0, first.PropertyChangedSubscriptionCount);
  433. Assert.Equal(0, second.PropertyChangedSubscriptionCount);
  434. GC.KeepAlive(first);
  435. GC.KeepAlive(second);
  436. }
  437. [Fact]
  438. public void Should_Not_Keep_Source_Alive()
  439. {
  440. Func<Tuple<ExpressionObserver, WeakReference>> run = () =>
  441. {
  442. var source = new Class1 { Foo = "foo" };
  443. var target = ExpressionObserver.Create(source, o => o.Foo);
  444. return Tuple.Create(target, new WeakReference(source));
  445. };
  446. var result = run();
  447. result.Item1.Subscribe(x => { });
  448. // Mono trickery
  449. GC.Collect(2);
  450. GC.WaitForPendingFinalizers();
  451. GC.WaitForPendingFinalizers();
  452. GC.Collect(2);
  453. Assert.Null(result.Item2.Target);
  454. }
  455. [Fact]
  456. public void Should_Not_Throw_Exception_On_Unsubscribe_When_Already_Unsubscribed()
  457. {
  458. var source = new Class1 { Foo = "foo" };
  459. var target = new PropertyAccessorNode("Foo", false);
  460. Assert.NotNull(target);
  461. target.Target = new WeakReference<object>(source);
  462. target.Subscribe(_ => { });
  463. target.Unsubscribe();
  464. target.Unsubscribe();
  465. Assert.True(true);
  466. }
  467. [Fact]
  468. public void Should_Not_Throw_Exception_When_Enabling_Data_Validation_On_Missing_Member()
  469. {
  470. var source = new Class1();
  471. var target = new PropertyAccessorNode("NotFound", true);
  472. target.Target = new WeakReference<object>(source);
  473. var result = new List<object>();
  474. target.Subscribe(x => result.Add(x));
  475. Assert.Equal(
  476. new object[]
  477. {
  478. new BindingNotification(
  479. new MissingMemberException("Could not find a matching property accessor for 'NotFound' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+Class1'"),
  480. BindingErrorType.Error),
  481. },
  482. result);
  483. }
  484. [Fact]
  485. public void Should_Not_Throw_Exception_On_Duplicate_Properties()
  486. {
  487. // Repro of https://github.com/AvaloniaUI/Avalonia/issues/4733.
  488. var source = new MyViewModel();
  489. var target = new PropertyAccessorNode("Name", false);
  490. target.Target = new WeakReference<object>(source);
  491. var result = new List<object>();
  492. target.Subscribe(x => result.Add(x));
  493. }
  494. [Fact]
  495. public void RootGetter_Is_Reevaluated_On_Subscribe()
  496. {
  497. var data = "foo";
  498. var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject<ValueTuple>(), null);
  499. var result = new List<object>();
  500. var sub = target.Subscribe(x => result.Add(x));
  501. Assert.Equal(new object[] { "foo" }, result);
  502. sub.Dispose();
  503. data = "bar";
  504. target.Subscribe(x => result.Add(x));
  505. Assert.Equal(new object[] { "foo", "bar" }, result);
  506. }
  507. public class MyViewModelBase { public object Name => "Name"; }
  508. public class MyViewModel : MyViewModelBase { public new string Name => "NewName"; }
  509. private interface INext
  510. {
  511. int PropertyChangedSubscriptionCount { get; }
  512. }
  513. private class Class1 : NotifyingBase
  514. {
  515. private string _foo;
  516. private INext _next;
  517. public string Foo
  518. {
  519. get { return _foo; }
  520. set
  521. {
  522. _foo = value;
  523. RaisePropertyChanged(nameof(Foo));
  524. }
  525. }
  526. private string _bar;
  527. public string Bar
  528. {
  529. get { return _bar; }
  530. set { _bar = value; }
  531. }
  532. public INext Next
  533. {
  534. get { return _next; }
  535. set
  536. {
  537. _next = value;
  538. RaisePropertyChanged(nameof(Next));
  539. }
  540. }
  541. }
  542. private class Class2 : NotifyingBase, INext
  543. {
  544. private string _bar;
  545. private INext _next;
  546. public string Bar
  547. {
  548. get { return _bar; }
  549. set
  550. {
  551. _bar = value;
  552. RaisePropertyChanged(nameof(Bar));
  553. }
  554. }
  555. public INext Next
  556. {
  557. get { return _next; }
  558. set
  559. {
  560. _next = value;
  561. RaisePropertyChanged(nameof(Next));
  562. }
  563. }
  564. }
  565. private class Class3 : Class1
  566. {
  567. }
  568. private class WithoutBar : NotifyingBase, INext
  569. {
  570. }
  571. private static Recorded<Notification<T>> OnNext<T>(long time, T value)
  572. {
  573. return new Recorded<Notification<T>>(time, Notification.CreateOnNext<T>(value));
  574. }
  575. }
  576. }