diff --git a/.editorconfig b/.editorconfig index 1c30f3da38..7d3342f42f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,12 +7,9 @@ root = true # Default settings ############################################# [*] -# UTF-8 without a byte-order mark (the "utf-8-bom" value would add one). -charset = utf-8 insert_final_newline = true indent_style = space indent_size = 4 -trim_trailing_whitespace = true [project.json] indent_size = 2 @@ -191,7 +188,7 @@ dotnet_diagnostic.CA1000.severity = none # Do not declare static members on gene dotnet_diagnostic.CA1001.severity = error # Types that own disposable fields should be disposable dotnet_diagnostic.CA1002.severity = none # Do not expose generic lists — we deliberately expose List; interface-based collections are an older convention we don't follow dotnet_diagnostic.CA1003.severity = error # Use generic event handler instances -dotnet_diagnostic.CA1005.severity = error # Avoid excessive parameters on generic types +dotnet_diagnostic.CA1005.severity = none # Avoid excessive parameters on generic types — we deliberately expose 3+ type-parameter types (tuple-style handles, raw engine signals); the ergonomic guidance conflicts with that design dotnet_diagnostic.CA1008.severity = error # Enums should have zero value dotnet_diagnostic.CA1010.severity = none # Collections should implement generic interface — we deliberately expose concrete collection types; interface-based collections are an older convention we don't follow dotnet_diagnostic.CA1012.severity = error # Abstract types should not have public constructors @@ -210,11 +207,11 @@ dotnet_diagnostic.CA1032.severity = error # Implement standard exception constru dotnet_diagnostic.CA1033.severity = none # Interface methods should be callable by child types — explicit interface implementations are a deliberate design choice dotnet_diagnostic.CA1034.severity = none # Nested types should not be visible — public nested types are sometimes the cleanest API (e.g. interface-scoped exception helpers) dotnet_diagnostic.CA1036.severity = none # Override methods on comparable types — relational operators rarely meaningful for our types -dotnet_diagnostic.CA1040.severity = none # Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = none # Avoid empty interfaces — duplicate of SST1437 (canonical); marker interfaces (IActivatableView etc.) are deliberate public API dotnet_diagnostic.CA1041.severity = error # Provide ObsoleteAttribute message dotnet_diagnostic.CA1043.severity = error # Use integral or string argument for indexers dotnet_diagnostic.CA1044.severity = error # Properties should not be write only -dotnet_diagnostic.CA1045.severity = error # Do not pass types by reference +dotnet_diagnostic.CA1045.severity = none # Do not pass types by reference — we deliberately use ref-passing static helpers so they carry only the data they touch; data-oriented layout is the default here dotnet_diagnostic.CA1046.severity = error # Do not overload operator equals on reference types dotnet_diagnostic.CA1047.severity = error # Do not declare protected member in sealed type dotnet_diagnostic.CA1048.severity = error # Do not declare virtual members in sealed types @@ -723,70 +720,71 @@ dotnet_diagnostic.SYSLIB1104.severity = error # Configuration binding source gen # Bug catchers — always error. dotnet_diagnostic.IDE0035.severity = error # Remove unreachable code dotnet_diagnostic.IDE0043.severity = error # Format string contains invalid placeholder -dotnet_diagnostic.IDE0051.severity = error # Remove unused private member -dotnet_diagnostic.IDE0052.severity = error # Remove unread private member +dotnet_diagnostic.IDE0051.severity = none # Remove unused private member — covered by SST1440 +dotnet_diagnostic.IDE0052.severity = none # Remove unread private member — covered by SST1441 dotnet_diagnostic.IDE0076.severity = error # Remove invalid global SuppressMessage attribute dotnet_diagnostic.IDE0077.severity = error # Avoid legacy format target in global SuppressMessage attribute -dotnet_diagnostic.IDE0080.severity = error # Remove unnecessary suppression operator +dotnet_diagnostic.IDE0080.severity = none # Remove unnecessary suppression operator — covered by SST2209 # Simplification / cleanup. -dotnet_diagnostic.IDE0001.severity = error # Simplify name -dotnet_diagnostic.IDE0002.severity = error # Simplify member access -dotnet_diagnostic.IDE0004.severity = none # Remove unnecessary cast — disabled because we deliberately keep explicit casts in Apple platform-specific paths and other reflection/marshalling sites where the analyzer's "redundant" judgement is wrong +dotnet_diagnostic.IDE0001.severity = none # Simplify name — covered by SST1116 +dotnet_diagnostic.IDE0002.severity = none # Simplify member access — covered by SST1117 +dotnet_diagnostic.IDE0004.severity = none # Remove unnecessary cast — covered by SST1175 dotnet_diagnostic.IDE0005.severity = none # Remove unnecessary import - DUPLICATE S1128 (slower: 2.364s vs 551ms) -dotnet_diagnostic.IDE0016.severity = error # Use throw expression -dotnet_diagnostic.IDE0017.severity = error # Use object initializers -dotnet_diagnostic.IDE0018.severity = error # Inline variable declaration -dotnet_diagnostic.IDE0019.severity = error # Use pattern matching to avoid 'as' followed by a 'null' check -dotnet_diagnostic.IDE0020.severity = error # Use pattern matching to avoid 'is' check followed by a cast -dotnet_diagnostic.IDE0028.severity = error # Use collection initializers -dotnet_diagnostic.IDE0029.severity = none # Use coalesce expression — handled by RCS1128 -dotnet_diagnostic.IDE0030.severity = error # Use coalesce expression (nullable types) -dotnet_diagnostic.IDE0031.severity = none # Use null propagation — handled by RCS1146 -dotnet_diagnostic.IDE0032.severity = none # Use auto property — handled by RCS1085 -dotnet_diagnostic.IDE0033.severity = error # Use explicitly provided tuple name -dotnet_diagnostic.IDE0034.severity = error # Simplify default expression -dotnet_diagnostic.IDE0037.severity = error # Use inferred member name -dotnet_diagnostic.IDE0038.severity = error # Use pattern matching ('is' check without a cast) -dotnet_diagnostic.IDE0041.severity = error # Use 'is null' check -dotnet_diagnostic.IDE0042.severity = error # Deconstruct variable declaration -dotnet_diagnostic.IDE0044.severity = error # Add readonly modifier -dotnet_diagnostic.IDE0046.severity = suggestion # Use conditional expression for return — downgraded because nested coalescing/ternary chains it produces hurt readability +dotnet_diagnostic.IDE0016.severity = none # Use throw expression — covered by SST2207 +dotnet_diagnostic.IDE0017.severity = none # Use object initializers — covered by SST1193 +dotnet_diagnostic.IDE0018.severity = none # Inline variable declaration — covered by SST2208 +dotnet_diagnostic.IDE0019.severity = none # Use pattern matching to avoid 'as' followed by a 'null' check — covered by SST2005 +dotnet_diagnostic.IDE0020.severity = none # Use pattern matching to avoid 'is' check followed by a cast — covered by SST2007 +dotnet_diagnostic.IDE0028.severity = none # Use collection initializers — covered by SST1194 +dotnet_diagnostic.IDE0029.severity = none # Use coalesce expression — covered by SST1195 +dotnet_diagnostic.IDE0030.severity = none # Use coalesce expression (nullable types) — covered by SST1195/SST2227 +dotnet_diagnostic.IDE0031.severity = none # Use null propagation — covered by SST1196 +dotnet_diagnostic.IDE0032.severity = none # Use auto property — covered by SST1420 +dotnet_diagnostic.IDE0033.severity = none # Use explicitly provided tuple name — covered by SST1142 +dotnet_diagnostic.IDE0034.severity = none # Simplify default expression — covered by SST1188 +dotnet_diagnostic.IDE0037.severity = none # Use inferred member name — covered by SST1173/SST2216 +dotnet_diagnostic.IDE0038.severity = none # Use pattern matching ('is' check without a cast) — covered by SST2007 +dotnet_diagnostic.IDE0041.severity = none # Use 'is null' check — covered by SST1149/SST2231 +dotnet_diagnostic.IDE0042.severity = none # Deconstruct variable declaration — covered by SST2214 +dotnet_diagnostic.IDE0044.severity = none # Add readonly modifier — covered by SST1424 +dotnet_diagnostic.IDE0046.severity = none # Use conditional expression for return — covered by SST1197 dotnet_diagnostic.IDE0047.severity = error # Remove unnecessary parentheses -dotnet_diagnostic.IDE0049.severity = error # Use language keywords instead of framework type names for type references -dotnet_diagnostic.IDE0054.severity = error # Use compound assignment -dotnet_diagnostic.IDE0056.severity = suggestion # Use index operator -dotnet_diagnostic.IDE0057.severity = suggestion # Use range operator -dotnet_diagnostic.IDE0059.severity = error # Remove unnecessary value assignment +dotnet_diagnostic.IDE0049.severity = none # Use language keywords instead of framework type names for type references — covered by SST1121 +dotnet_diagnostic.IDE0054.severity = none # Use compound assignment — covered by SST1185 +dotnet_diagnostic.IDE0056.severity = none # Use index operator — covered by SST2203 +dotnet_diagnostic.IDE0057.severity = none # Use range operator — covered by SST2204 +dotnet_diagnostic.IDE0059.severity = none # Remove unnecessary value assignment — covered by SST2222 dotnet_diagnostic.IDE0062.severity = error # Make local function static dotnet_diagnostic.IDE0063.severity = error # Use simple 'using' statement dotnet_diagnostic.IDE0064.severity = error # Make struct fields writable — flag readonly-but-mutable struct fields where intent diverges from declaration -dotnet_diagnostic.IDE0066.severity = error # Use switch expression -dotnet_diagnostic.IDE0070.severity = error # Use 'System.HashCode.Combine' -dotnet_diagnostic.IDE0071.severity = error # Simplify interpolation -dotnet_diagnostic.IDE0072.severity = suggestion # Add missing cases to switch expression - sometimes we miss switch cases dilberately. -dotnet_diagnostic.IDE0074.severity = error # Use compound assignment -dotnet_diagnostic.IDE0075.severity = error # Simplify conditional expression -dotnet_diagnostic.IDE0078.severity = error # Use pattern matching -dotnet_diagnostic.IDE0082.severity = error # Convert typeof to nameof -dotnet_diagnostic.IDE0083.severity = error # Use pattern matching ('not' operator) +dotnet_diagnostic.IDE0066.severity = none # Use switch expression — covered by SST2201 +dotnet_diagnostic.IDE0070.severity = none # Use 'System.HashCode.Combine' — covered by SST2217 +dotnet_diagnostic.IDE0071.severity = none # Simplify interpolation — covered by SST2220 +dotnet_diagnostic.IDE0072.severity = none # Add missing cases to switch expression — covered by SST2206 +dotnet_diagnostic.IDE0074.severity = none # Use compound assignment — covered by SST1185 +dotnet_diagnostic.IDE0075.severity = none # Simplify conditional expression — covered by SST1182 +dotnet_diagnostic.IDE0078.severity = none # Use pattern matching — covered by SST2006/SST2231 +dotnet_diagnostic.IDE0082.severity = none # Convert typeof to nameof — covered by SST1199 +dotnet_diagnostic.IDE0083.severity = none # Use pattern matching ('not' operator) — covered by SST2006 dotnet_diagnostic.IDE0084.severity = error # Use pattern matching ('IsNot' operator) -dotnet_diagnostic.IDE0090.severity = error # Simplify 'new' expression -dotnet_diagnostic.IDE0100.severity = error # Remove unnecessary equality operator -dotnet_diagnostic.IDE0110.severity = error # Remove unnecessary discard -dotnet_diagnostic.IDE0120.severity = error # Simplify LINQ expression -dotnet_diagnostic.IDE0150.severity = error # Prefer 'null' check over type check +dotnet_diagnostic.IDE0090.severity = none # Simplify 'new' expression — covered by SST2202 +dotnet_diagnostic.IDE0100.severity = none # Remove unnecessary equality operator — covered by SST1143 +dotnet_diagnostic.IDE0110.severity = none # Remove unnecessary discard — covered by SST2213 +dotnet_diagnostic.IDE0120.severity = none # Simplify LINQ expression — covered by SST2229/SST2233 +dotnet_diagnostic.IDE0150.severity = none # Prefer 'null' check over type check — covered by SST2231 dotnet_diagnostic.IDE0161.severity = error # Use file-scoped namespace declaration dotnet_diagnostic.IDE0170.severity = error # Simplify property pattern -dotnet_diagnostic.IDE0180.severity = error # Use tuple to swap values +dotnet_diagnostic.IDE0180.severity = none # Use tuple to swap values — covered by SST2215 dotnet_diagnostic.IDE0200.severity = error # Remove unnecessary lambda expression -dotnet_diagnostic.IDE0220.severity = error # Add explicit cast in foreach loop -dotnet_diagnostic.IDE0240.severity = error # Remove redundant nullable directive -dotnet_diagnostic.IDE0241.severity = error # Remove unnecessary nullable directive +dotnet_diagnostic.IDE0220.severity = none # Add explicit cast in foreach loop — covered by SST2225 +dotnet_diagnostic.IDE0221.severity = none # Add explicit cast — covered by SST2226 +dotnet_diagnostic.IDE0240.severity = none # Remove redundant nullable directive — covered by SST2210 +dotnet_diagnostic.IDE0241.severity = none # Remove unnecessary nullable directive — covered by SST2211 dotnet_diagnostic.IDE0250.severity = error # Make struct 'readonly' dotnet_diagnostic.IDE0251.severity = error # Make member 'readonly' -dotnet_diagnostic.IDE0260.severity = error # Use pattern matching -dotnet_diagnostic.IDE0270.severity = error # Use coalesce expression +dotnet_diagnostic.IDE0260.severity = none # Use pattern matching — covered by SST2006/SST2231 +dotnet_diagnostic.IDE0270.severity = none # Use coalesce expression — covered by SST2227 # Disabled — conflict with an existing SA/CA rule or project convention. dotnet_diagnostic.IDE0003.severity = none # Remove 'this' qualifier — we don't follow the this. prefix style (SA1101 = none) @@ -803,13 +801,13 @@ dotnet_diagnostic.IDE0025.severity = none # Use expression body for properties dotnet_diagnostic.IDE0026.severity = none # Use expression body for indexers — preference not enforced dotnet_diagnostic.IDE0027.severity = none # Use expression body for accessors — preference not enforced dotnet_diagnostic.IDE0036.severity = none # Order modifiers — SA1206 handles -dotnet_diagnostic.IDE0039.severity = none # Use local function — anonymous method preference not enforced +dotnet_diagnostic.IDE0039.severity = none # Use local function — covered by SST2228 dotnet_diagnostic.IDE0040.severity = none # Add accessibility modifiers — SA1400 handles -dotnet_diagnostic.IDE0045.severity = none # Use conditional expression for assignment — preference not enforced +dotnet_diagnostic.IDE0045.severity = none # Use conditional expression for assignment — covered by SST1198 dotnet_diagnostic.IDE0048.severity = none # Add parentheses for clarity — preference not enforced dotnet_diagnostic.IDE0053.severity = none # Use expression body for lambdas — preference not enforced dotnet_diagnostic.IDE0055.severity = none # Formatting — StyleCop SA rules own formatting -dotnet_diagnostic.IDE0058.severity = none # Remove unnecessary expression value — too noisy (ignores fluent APIs) +dotnet_diagnostic.IDE0058.severity = none # Remove unnecessary expression value — covered by SST2221 dotnet_diagnostic.IDE0060.severity = none # Remove unused parameter — CA1801 handles (with api_surface config) dotnet_diagnostic.IDE0061.severity = none # Use expression body for local function — preference not enforced dotnet_diagnostic.IDE0065.severity = none # 'using' directive placement — SA1200 = none (we put usings outside file-scoped namespaces) @@ -821,17 +819,20 @@ dotnet_diagnostic.IDE0210.severity = none # Convert to top-level statements — dotnet_diagnostic.IDE0211.severity = none # Convert to 'Program.Main' style — we use Main style dotnet_diagnostic.IDE0280.severity = none # Use nameof — CA1507 / RCS1015 handle this dotnet_diagnostic.IDE0290.severity = none # Use primary constructor — subjective, not enforced -dotnet_diagnostic.IDE0300.severity = error # Use collection expression for array -dotnet_diagnostic.IDE0301.severity = error # Use collection expression for empty -dotnet_diagnostic.IDE0302.severity = error # Use collection expression for stackalloc -dotnet_diagnostic.IDE0303.severity = error # Use collection expression for Create -dotnet_diagnostic.IDE0304.severity = error # Use collection expression for builder -dotnet_diagnostic.IDE0305.severity = error # Use collection expression for fluent -dotnet_diagnostic.IDE0306.severity = error # Use collection expression for new +dotnet_diagnostic.IDE0300.severity = none # Use collection expression for array — covered by SST2101 +dotnet_diagnostic.IDE0301.severity = none # Use collection expression for empty — covered by SST2100 +dotnet_diagnostic.IDE0302.severity = none # Use collection expression for stackalloc — covered by SST2102 +dotnet_diagnostic.IDE0303.severity = none # Use collection expression for Create — covered by SST2103 +dotnet_diagnostic.IDE0304.severity = none # Use collection expression for builder — covered by SST2104 +dotnet_diagnostic.IDE0305.severity = none # Use collection expression for fluent — covered by SST2105 +dotnet_diagnostic.IDE0306.severity = none # Use collection expression for new — covered by SST2101 dotnet_diagnostic.IDE0320.severity = error # Make anonymous function static — eliminates the closure-capture display class allocation -dotnet_diagnostic.IDE0050.severity = error # Convert anonymous type to tuple -dotnet_diagnostic.IDE0230.severity = error # Use UTF-8 string literal — better hot-path encoding for byte-array constants +dotnet_diagnostic.IDE0050.severity = none # Convert anonymous type to tuple — covered by SST2224 +dotnet_diagnostic.IDE0230.severity = none # Use UTF-8 string literal — covered by SST2212 dotnet_diagnostic.IDE0310.severity = none # Convert lambda expression to method group — handled by RCS1207 +dotnet_diagnostic.IDE0340.severity = none # Use unbound generic type — covered by SST2232 +dotnet_diagnostic.IDE0350.severity = none # Use implicitly typed lambda — covered by SST2218 +dotnet_diagnostic.IDE0360.severity = none # Simplify property accessor — covered by SST2219 dotnet_diagnostic.IDE0370.severity = none # Remove unnecessary suppression (null-forgiving operator) — disabled for the same reason as RCS1249: multi-TFM nullability annotations can differ per platform dotnet_diagnostic.IDE0380.severity = error # Remove unnecessary unsafe modifier dotnet_diagnostic.IDE1005.severity = error # Use conditional delegate call @@ -852,7 +853,7 @@ dotnet_diagnostic.IDE3000.severity = none # Disabled per project convention — ################### dotnet_diagnostic.RCS1001.severity = error # Add braces (when expression spans over multiple lines) dotnet_diagnostic.RCS1003.severity = error # Add braces to if-else (when expression spans over multiple lines) -dotnet_diagnostic.RCS1005.severity = none # Conflicts with SST1519 (canonical) which requires braces around multi-line using children +dotnet_diagnostic.RCS1005.severity = error # Simplify nested using statement dotnet_diagnostic.RCS1006.severity = error # Merge 'else' with nested 'if' dotnet_diagnostic.RCS1007.severity = error # Add braces dotnet_diagnostic.RCS1031.severity = none # Remove unnecessary braces in switch section -- we don't mind braces in switch statements @@ -927,7 +928,6 @@ dotnet_diagnostic.RCS1015.severity = error # Use nameof operator dotnet_diagnostic.RCS1016.severity = error # Use block body or expression body dotnet_diagnostic.RCS1020.severity = error # Simplify Nullable to T? dotnet_diagnostic.RCS1021.severity = error # Convert lambda expression body to expression body -dotnet_diagnostic.RCS1040.severity = none # covered by SST1180 dotnet_diagnostic.RCS1044.severity = none # Remove original exception from throw statement — covered by SST1430 dotnet_diagnostic.RCS1046.severity = none # Asynchronous method name should end with 'Async' — TUnit test method naming convention doesn't follow the Async suffix — covered by SST1317 dotnet_diagnostic.RCS1047.severity = error # Non-asynchronous method name should not end with 'Async' @@ -1133,6 +1133,11 @@ stylesharp.document_private_elements = true stylesharp.document_private_fields = true stylesharp.document_interfaces = all stylesharp.SST1305.allowed_hungarian_prefixes = rx +stylesharp.instance_member_qualification = omit_this +stylesharp.avoid_linq_on_hot_path = true +stylesharp.max_cyclomatic_complexity = 10 +stylesharp.max_cognitive_complexity = 15 +stylesharp.max_property_cognitive_complexity = 3 # Spacing dotnet_diagnostic.SST1000.severity = error # A control-flow keyword is not followed by a space @@ -1180,6 +1185,8 @@ dotnet_diagnostic.SST1112.severity = error # An empty parameter list's closing p dotnet_diagnostic.SST1113.severity = error # A comma does not sit on the previous parameter's line dotnet_diagnostic.SST1114.severity = error # A blank line separates the declaration from its parameter list dotnet_diagnostic.SST1115.severity = error # A blank line separates a parameter from the preceding comma +dotnet_diagnostic.SST1116.severity = error # A qualified name can be shortened without changing the symbol it binds to +dotnet_diagnostic.SST1117.severity = error # Instance member access follows the configured this. qualification style dotnet_diagnostic.SST1118.severity = none # A parameter should not span multiple lines dotnet_diagnostic.SST1120.severity = error # A comment contains no text dotnet_diagnostic.SST1121.severity = none # A framework type name is used instead of its built-in alias — duplicate of RCS1013 @@ -1199,6 +1206,7 @@ dotnet_diagnostic.SST1135.severity = error # A using directive names a namespace dotnet_diagnostic.SST1136.severity = error # Several enum members share a line dotnet_diagnostic.SST1137.severity = error # Sibling elements are indented differently from one another dotnet_diagnostic.SST1139.severity = error # A numeric literal is cast where a literal suffix would express the type +dotnet_diagnostic.SST1140.severity = error # Wrapped conditional operators should start indented continuation lines dotnet_diagnostic.SST1141.severity = error # An explicit ValueTuple<...> is used where tuple syntax would do dotnet_diagnostic.SST1142.severity = error # A tuple element is accessed by ItemN where it has a name dotnet_diagnostic.SST1143.severity = error # A boolean expression is compared to a true/false literal @@ -1251,6 +1259,13 @@ dotnet_diagnostic.SST1189.severity = error # Variables should not be self-assign dotnet_diagnostic.SST1190.severity = error # Doubled negation operators should be removed dotnet_diagnostic.SST1191.severity = error # Long numeric literals should use digit separators dotnet_diagnostic.SST1192.severity = none # Control characters in string literals should be escaped +dotnet_diagnostic.SST1193.severity = error # Keep initial member values with construction +dotnet_diagnostic.SST1194.severity = error # Keep initial collection values with construction +dotnet_diagnostic.SST1195.severity = error # Write null fallback with ?? +dotnet_diagnostic.SST1196.severity = error # Write null-guarded access with ?. +dotnet_diagnostic.SST1197.severity = error # Collapse return-only branches into one return +dotnet_diagnostic.SST1198.severity = error # Collapse assignment-only branches into one assignment +dotnet_diagnostic.SST1199.severity = error # Prefer compile-time type names # Ordering dotnet_diagnostic.SST1200.severity = none # Using directives should be placed outside the namespace — usings live outside file-scoped namespaces @@ -1294,7 +1309,7 @@ dotnet_diagnostic.SST1318.severity = error # Overriding parameter names should m # Maintainability dotnet_diagnostic.SST1400.severity = error # An element does not declare an access modifier -dotnet_diagnostic.SST1401.severity = error # A non-private, non-constant field is exposed (canonical rule; CA1051/S2357 disabled as duplicates) +dotnet_diagnostic.SST1401.severity = error # A non-private, non-constant field is exposed dotnet_diagnostic.SST1402.severity = error # A file declares more than one top-level type dotnet_diagnostic.SST1403.severity = error # A file declares more than one namespace dotnet_diagnostic.SST1404.severity = error # A code-analysis suppression has no justification @@ -1332,6 +1347,11 @@ dotnet_diagnostic.SST1436.severity = error # Empty types should not be declared dotnet_diagnostic.SST1437.severity = error # Empty interfaces should not be declared dotnet_diagnostic.SST1438.severity = error # Methods should not be empty dotnet_diagnostic.SST1439.severity = error # Nested code blocks should not be left empty +dotnet_diagnostic.SST1440.severity = error # Private members with no local use should be removed +dotnet_diagnostic.SST1441.severity = error # Private fields assigned but never read should be removed +dotnet_diagnostic.SST1442.severity = error # A function has too many direct branch points +dotnet_diagnostic.SST1443.severity = error # A function has too much nested control flow +dotnet_diagnostic.SST1444.severity = error # A loop cannot naturally reach a second iteration dotnet_diagnostic.SST1450.severity = error # Store files as UTF-8 without a byte order mark # Layout @@ -1416,6 +1436,7 @@ dotnet_diagnostic.SST2003.severity = suggestion # A disposed check should use Ob dotnet_diagnostic.SST2004.severity = suggestion # A range check should use an ArgumentOutOfRangeException.ThrowIf... helper dotnet_diagnostic.SST2005.severity = error # Use the 'is' type pattern instead of comparing an 'as' cast to null dotnet_diagnostic.SST2006.severity = error # Use the 'is not' pattern instead of negating an 'is' check +dotnet_diagnostic.SST2007.severity = error # Use declaration patterns instead of an is check followed by a cast local # Modern language and library usage dotnet_diagnostic.SST1700.severity = error # An extension block declares no members @@ -1432,7 +1453,45 @@ dotnet_diagnostic.SST1802.severity = error # A record declares a settable rather dotnet_diagnostic.SST1803.severity = error # A record struct is not declared readonly dotnet_diagnostic.SST2100.severity = error # An empty collection creation can use [] dotnet_diagnostic.SST2101.severity = error # An explicit collection creation can use [...] +dotnet_diagnostic.SST2102.severity = error # A span-targeted stackalloc initializer can use a collection expression +dotnet_diagnostic.SST2103.severity = error # A collection-builder factory call can use a collection expression +dotnet_diagnostic.SST2104.severity = error # A short builder-local sequence can return a collection expression +dotnet_diagnostic.SST2105.severity = error # A literal array materialized with LINQ can use the target collection expression directly dotnet_diagnostic.SST2200.severity = error # A single-use backing field can use the C# 14 field keyword +dotnet_diagnostic.SST2201.severity = error # A return-only switch statement can use a switch expression +dotnet_diagnostic.SST2202.severity = error # An object creation repeats an explicit target type +dotnet_diagnostic.SST2203.severity = error # An array or string index can use from-end indexing +dotnet_diagnostic.SST2204.severity = error # A string slice can use range syntax +dotnet_diagnostic.SST2205.severity = none # An enum switch statement omits named enum values — duplicate of IDE0010 (disabled: too noisy for default/fallthrough) and S131; statement switches deliberately no-op omitted values, and a default to satisfy it is rejected by SST1179/S3532. SST2206 keeps the valuable switch-expression exhaustiveness check. +dotnet_diagnostic.SST2206.severity = error # An enum switch expression omits named enum values +dotnet_diagnostic.SST2207.severity = error # A null guard and return can keep the throw in the returned expression +dotnet_diagnostic.SST2208.severity = error # An out variable can be declared at the call site +dotnet_diagnostic.SST2209.severity = none # A null-forgiving operator has no local effect +dotnet_diagnostic.SST2210.severity = error # A nullable directive repeats the current file-local state +dotnet_diagnostic.SST2211.severity = error # A nullable restore directive has no file-local state to restore +dotnet_diagnostic.SST2212.severity = error # Literal UTF-8 byte data can use a u8 string literal +dotnet_diagnostic.SST2213.severity = error # A typed pattern has an unnecessary discard designation +dotnet_diagnostic.SST2214.severity = error # A tuple temporary only feeds copied element locals +dotnet_diagnostic.SST2215.severity = error # A temporary local swaps two locals +dotnet_diagnostic.SST2216.severity = error # A tuple element name repeats the inferred name +dotnet_diagnostic.SST2217.severity = error # A manual hash-code expression can use System.HashCode.Combine +dotnet_diagnostic.SST2218.severity = error # Lambda parameter types can be omitted when the target already supplies them +dotnet_diagnostic.SST2219.severity = error # A single-expression property accessor can use an expression body +dotnet_diagnostic.SST2220.severity = error # An interpolation hole can carry the value and literal format directly +dotnet_diagnostic.SST2221.severity = error # An ignored expression value is assigned to the discard +dotnet_diagnostic.SST2222.severity = error # A local value is overwritten before it is read +dotnet_diagnostic.SST2223.severity = error # A null fallback assignment can use ??= +dotnet_diagnostic.SST2224.severity = error # An anonymous object can become a tuple literal for local value bundles +dotnet_diagnostic.SST2225.severity = error # A foreach loop hides an explicit element cast +dotnet_diagnostic.SST2226.severity = error # A cast hides an inner explicit conversion +dotnet_diagnostic.SST2227.severity = error # A post-assignment null fallback can be folded into the assigned expression +dotnet_diagnostic.SST2228.severity = error # A delegate local used only as a call target can be a local function +dotnet_diagnostic.SST2229.severity = none # LINQ terminal predicate simplification is reserved for test code; production code should avoid LINQ on hot paths +dotnet_diagnostic.SST2230.severity = none # LINQ type-filter simplification is reserved for test code; production code should avoid LINQ on hot paths +dotnet_diagnostic.SST2231.severity = error # A broad object pattern can use a direct null pattern +dotnet_diagnostic.SST2232.severity = error # nameof does not need concrete generic type arguments +dotnet_diagnostic.SST2233.severity = error # Hot-path code should avoid System.Linq.Enumerable calls +dotnet_diagnostic.RCS1040.severity = none # covered by SST1180 ################### # PublicApiAnalyzers (RSxxxx) - public API surface tracking @@ -1605,7 +1664,7 @@ dotnet_diagnostic.S6674.severity = error # Log message template should be syntac ################### dotnet_diagnostic.S1244.severity = error # Floating point numbers should not be tested for equality dotnet_diagnostic.S1656.severity = none # Variables should not be self-assigned — covered by SST1189 -dotnet_diagnostic.S1751.severity = error # Loops with at most one iteration should be refactored +dotnet_diagnostic.S1751.severity = none # Loops with at most one iteration should be refactored - covered by SST1444 dotnet_diagnostic.S1764.severity = error # Identical expressions should not be used on both sides of operators dotnet_diagnostic.S1848.severity = error # Objects should not be created to be dropped immediately without being used dotnet_diagnostic.S1862.severity = error # Related "if/else if" statements should not have the same condition @@ -1656,7 +1715,7 @@ dotnet_diagnostic.S6930.severity = error # Backslash should be avoided in route # SonarAnalyzer (Sxxxx) - Minor Bug ################### dotnet_diagnostic.S1206.severity = none # "Equals(Object)" and "GetHashCode()" should be overridden in pairs - DUPLICATE CA2218 -dotnet_diagnostic.S1226.severity = error # Method parameters, caught exceptions and foreach variables' initial values should not be ignored +dotnet_diagnostic.S1226.severity = none # Method parameters, caught exceptions and foreach variables' initial values should not be ignored dotnet_diagnostic.S2183.severity = error # Integral numbers should not be shifted by zero or more than their number of bits-1 dotnet_diagnostic.S2184.severity = error # Results of integer division should not be assigned to floating point variables dotnet_diagnostic.S2328.severity = error # "GetHashCode" should not reference mutable fields @@ -1737,7 +1796,7 @@ dotnet_diagnostic.S1215.severity = none # "GC.Collect" should not be called dotnet_diagnostic.S126.severity = none # "if ... else if" constructs should end with "else" clauses dotnet_diagnostic.S131.severity = none # "switch/Select" statements should contain a "default/Case Else" clauses dotnet_diagnostic.S134.severity = none # Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply -dotnet_diagnostic.S1541.severity = error # Methods and properties should not be too complex +dotnet_diagnostic.S1541.severity = none # Methods and properties should not be too complex - covered by SST1442 dotnet_diagnostic.S1699.severity = error # Constructors should only call non-overridable methods dotnet_diagnostic.S1821.severity = error # "switch" statements should not be nested dotnet_diagnostic.S1944.severity = error # Invalid casts should be avoided @@ -1766,7 +1825,7 @@ dotnet_diagnostic.S3353.severity = error # Unchanged variables should be marked dotnet_diagnostic.S3447.severity = error # "[Optional]" should not be used on "ref" or "out" parameters dotnet_diagnostic.S3451.severity = error # "[DefaultValue]" should not be used when "[DefaultParameterValue]" is meant dotnet_diagnostic.S3600.severity = error # "params" should not be introduced on overrides -dotnet_diagnostic.S3776.severity = error # Cognitive Complexity of methods should not be too high +dotnet_diagnostic.S3776.severity = none # Cognitive Complexity of methods should not be too high - covered by SST1443 dotnet_diagnostic.S3871.severity = error # Exception types should be "public" dotnet_diagnostic.S3874.severity = none # "out" and "ref" parameters — repo idiom is TryX(..., out T value) dotnet_diagnostic.S3904.severity = error # Assemblies should have version information @@ -1827,7 +1886,7 @@ dotnet_diagnostic.S2166.severity = none # Classes named like "Exception" should dotnet_diagnostic.S2234.severity = error # Arguments should be passed in the same order as the method parameters dotnet_diagnostic.S2326.severity = error # Unused type parameters should be removed dotnet_diagnostic.S2327.severity = error # "try" statements with identical "catch" and/or "finally" blocks should be merged -dotnet_diagnostic.S2357.severity = none # Duplicate of SST1401 (canonical) — fields should be private +dotnet_diagnostic.S2357.severity = none # Fields should be private — duplicate of SST1401 (canonical); fields intentionally exposed (e.g. public test fields for reflection) already carry per-site SST1401 suppressions dotnet_diagnostic.S2372.severity = error # Exceptions should not be thrown from property getters dotnet_diagnostic.S2376.severity = error # Write-only properties should not be used dotnet_diagnostic.S2629.severity = error # Logging templates should be constant @@ -1968,7 +2027,7 @@ dotnet_diagnostic.S3220.severity = error # Method calls should not resolve ambig dotnet_diagnostic.S3234.severity = error # "GC.SuppressFinalize" should not be invoked for types without destructors dotnet_diagnostic.S3235.severity = error # Redundant parentheses should not be used dotnet_diagnostic.S3236.severity = error # Caller information arguments should not be provided explicitly -dotnet_diagnostic.S3240.severity = error # The simplest possible condition syntax should be used +dotnet_diagnostic.S3240.severity = none # The simplest possible condition syntax should be used - duplicate of SST1198 dotnet_diagnostic.S3241.severity = error # Methods should not return values that are never used dotnet_diagnostic.S3242.severity = none # Method parameters should be declared with base types dotnet_diagnostic.S3247.severity = error # Duplicate casts should not be made @@ -2288,6 +2347,10 @@ end_of_line = crlf # cannot be marked static — even when a partial declaration happens to hold only static # members (the instance [Test] methods live in sibling partial files). [**/tests/**/*.cs] +stylesharp.avoid_linq_on_hot_path = false +dotnet_diagnostic.SST2229.severity = error # In tests, simplify LINQ Where plus terminal calls instead of banning LINQ +dotnet_diagnostic.SST2230.severity = error # In tests, simplify LINQ type filters instead of banning LINQ +dotnet_diagnostic.SST2233.severity = none # Tests can use LINQ for clarity dotnet_diagnostic.SST1432.severity = none dotnet_diagnostic.RCS1072.severity = none # covered by SST1435 dotnet_diagnostic.RCS1106.severity = none # covered by SST1434 diff --git a/README.md b/README.md index 433c185f29..ba2b4beed8 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,17 @@ routing/collection/auto-persist helpers now live in a separate **`ReactiveUI.Rou for the System.Reactive flavor), so core no longer depends on DynamicData. Add `ReactiveUI.Routing` if you use those extensions. +### Analyzers are opt-in + +The ReactiveUI packages reference `ReactiveUI.Primitives` with `ExcludeAssets="analyzers"`, so the analyzers that ship +inside ReactiveUI.Primitives **do not flow to your project** and will not run against your code just because you +installed ReactiveUI. We don't impose our analyzers on downstream consumers. If you want them, opt in explicitly by +adding a direct reference to `ReactiveUI.Primitives` (without excluding the analyzer assets), e.g.: + +```xml + +``` + [Core]: https://www.nuget.org/packages/ReactiveUI/ [CoreBadge]: https://img.shields.io/nuget/v/ReactiveUI.svg diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4bc0df81e5..da0973ee8f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -70,7 +70,7 @@ net462;net472;net481 - net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0;net11.0-windows10.0.19041.0 + net10.0-windows10.0.19041.0;net11.0-windows10.0.19041.0 net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0;net11.0-windows10.0.19041.0 net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0;net11.0-windows10.0.19041.0 diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index abb75dea9f..9f13fabf3e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -6,8 +6,8 @@ 20.0.0 - 5.4.0 - 1.54.0 + 5.6.0 + 1.56.25 2.10.0.2 @@ -78,7 +78,7 @@ - + diff --git a/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt b/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt index c66fdaa082..07b1e7a0da 100644 --- a/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt @@ -51,15 +51,15 @@ ReactiveUI.Reactive.AndroidX.ReactiveFragment.ViewModel.get -> TView ReactiveUI.Reactive.AndroidX.ReactiveFragment.ViewModel.set -> void ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Activated.get -> System.IObservable! -ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Changed.get -> System.IObservable!>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Changing.get -> System.IObservable!>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Deactivated.get -> System.IObservable! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.PropertyChanged -> System.ComponentModel.PropertyChangedEventHandler? ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.PropertyChanging -> System.ComponentModel.PropertyChangingEventHandler? ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ReactiveFragmentActivity() -> void -ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! -ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! +ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.SuppressChangeNotifications() -> System.IDisposable! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ThrownExceptions.get -> System.IObservable! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity diff --git a/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt b/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt index c66fdaa082..07b1e7a0da 100644 --- a/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.AndroidX.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt @@ -51,15 +51,15 @@ ReactiveUI.Reactive.AndroidX.ReactiveFragment.ViewModel.get -> TView ReactiveUI.Reactive.AndroidX.ReactiveFragment.ViewModel.set -> void ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Activated.get -> System.IObservable! -ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Changed.get -> System.IObservable!>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Changing.get -> System.IObservable!>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.Deactivated.get -> System.IObservable! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.PropertyChanged -> System.ComponentModel.PropertyChangedEventHandler? ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.PropertyChanging -> System.ComponentModel.PropertyChangingEventHandler? ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ReactiveFragmentActivity() -> void -ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! -ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! +ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.SuppressChangeNotifications() -> System.IDisposable! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity.ThrownExceptions.get -> System.IObservable! ReactiveUI.Reactive.AndroidX.ReactiveFragmentActivity diff --git a/src/ReactiveUI.AndroidX/ActivityResultAwaiter.cs b/src/ReactiveUI.AndroidX/ActivityResultAwaiter.cs new file mode 100644 index 0000000000..0b5fec5031 --- /dev/null +++ b/src/ReactiveUI.AndroidX/ActivityResultAwaiter.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Android.Content; + +#if REACTIVE_SHIM +namespace ReactiveUI.Reactive.AndroidX; +#else +namespace ReactiveUI.AndroidX; +#endif + +/// +/// Awaits the first activity result that matches a request code, then completes the task and detaches. A fused, +/// allocation-light replacement for Where(...).Select(...).FirstAsync().ToTask(): it is its own observer, +/// settles exactly once, and unsubscribes on completion. Shared by the AppCompat and Fragment reactive activities. +/// +internal sealed class ActivityResultAwaiter + : IObserver<(int requestCode, Result result, Intent? intent)>, IDisposable +{ + /// The request code this awaiter is waiting for. + private readonly int _requestCode; + + /// Completion source backing the returned task. + private readonly TaskCompletionSource<(Result result, Intent? intent)> _completion = new(); + + /// Holds the source subscription so it can be torn down once settled. + private readonly OnceDisposable _subscription = new(); + + /// Guards against settling more than once. + private int _settled; + + /// Initializes a new instance of the class. + /// The request code to await. + private ActivityResultAwaiter(int requestCode) => _requestCode = requestCode; + + /// Subscribes to the activity-result stream and returns a task for the first matching result. + /// The activity-result stream. + /// The request code to await. + /// A task that completes with the result and intent of the first matching activity result. + public static Task<(Result result, Intent? intent)> Await( + IObservable<(int requestCode, Result result, Intent? intent)> source, + int requestCode) + { + var awaiter = new ActivityResultAwaiter(requestCode); + awaiter._subscription.Disposable = source.Subscribe(awaiter); + return awaiter._completion.Task; + } + + /// + public void OnNext((int requestCode, Result result, Intent? intent) value) + { + if (value.requestCode != _requestCode || Interlocked.Exchange(ref _settled, 1) != 0) + { + return; + } + + _ = _completion.TrySetResult((value.result, value.intent)); + Dispose(); + } + + /// + public void OnError(Exception error) + { + if (Interlocked.Exchange(ref _settled, 1) != 0) + { + return; + } + + _ = _completion.TrySetException(error); + Dispose(); + } + + /// + public void OnCompleted() + { + if (Interlocked.Exchange(ref _settled, 1) != 0) + { + return; + } + + _ = _completion.TrySetCanceled(); + Dispose(); + } + + /// + public void Dispose() => _subscription.Dispose(); +} diff --git a/src/ReactiveUI.AndroidX/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt b/src/ReactiveUI.AndroidX/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt index 3f7e946d25..35484503ef 100644 --- a/src/ReactiveUI.AndroidX/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.AndroidX/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt @@ -51,15 +51,15 @@ ReactiveUI.AndroidX.ReactiveFragment.ViewModel.get -> TViewModel? ReactiveUI.AndroidX.ReactiveFragment.ViewModel.set -> void ReactiveUI.AndroidX.ReactiveFragmentActivity ReactiveUI.AndroidX.ReactiveFragmentActivity.Activated.get -> System.IObservable! -ReactiveUI.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.AndroidX.ReactiveFragmentActivity.Changed.get -> System.IObservable!>! ReactiveUI.AndroidX.ReactiveFragmentActivity.Changing.get -> System.IObservable!>! ReactiveUI.AndroidX.ReactiveFragmentActivity.Deactivated.get -> System.IObservable! ReactiveUI.AndroidX.ReactiveFragmentActivity.PropertyChanged -> System.ComponentModel.PropertyChangedEventHandler? ReactiveUI.AndroidX.ReactiveFragmentActivity.PropertyChanging -> System.ComponentModel.PropertyChangingEventHandler? ReactiveUI.AndroidX.ReactiveFragmentActivity.ReactiveFragmentActivity() -> void -ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! -ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! +ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.AndroidX.ReactiveFragmentActivity.SuppressChangeNotifications() -> System.IDisposable! ReactiveUI.AndroidX.ReactiveFragmentActivity.ThrownExceptions.get -> System.IObservable! ReactiveUI.AndroidX.ReactiveFragmentActivity diff --git a/src/ReactiveUI.AndroidX/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt b/src/ReactiveUI.AndroidX/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt index 3f7e946d25..35484503ef 100644 --- a/src/ReactiveUI.AndroidX/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.AndroidX/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt @@ -51,15 +51,15 @@ ReactiveUI.AndroidX.ReactiveFragment.ViewModel.get -> TViewModel? ReactiveUI.AndroidX.ReactiveFragment.ViewModel.set -> void ReactiveUI.AndroidX.ReactiveFragmentActivity ReactiveUI.AndroidX.ReactiveFragmentActivity.Activated.get -> System.IObservable! -ReactiveUI.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.AndroidX.ReactiveFragmentActivity.ActivityResult.get -> System.IObservable<(int requestCode, Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.AndroidX.ReactiveFragmentActivity.Changed.get -> System.IObservable!>! ReactiveUI.AndroidX.ReactiveFragmentActivity.Changing.get -> System.IObservable!>! ReactiveUI.AndroidX.ReactiveFragmentActivity.Deactivated.get -> System.IObservable! ReactiveUI.AndroidX.ReactiveFragmentActivity.PropertyChanged -> System.ComponentModel.PropertyChangedEventHandler? ReactiveUI.AndroidX.ReactiveFragmentActivity.PropertyChanging -> System.ComponentModel.PropertyChangingEventHandler? ReactiveUI.AndroidX.ReactiveFragmentActivity.ReactiveFragmentActivity() -> void -ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! -ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent! intent)>! +ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(Android.Content.Intent! intent, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! +ReactiveUI.AndroidX.ReactiveFragmentActivity.StartActivityForResultAsync(System.Type! type, int requestCode) -> System.Threading.Tasks.Task<(Android.App.Result result, Android.Content.Intent? intent)>! ReactiveUI.AndroidX.ReactiveFragmentActivity.SuppressChangeNotifications() -> System.IDisposable! ReactiveUI.AndroidX.ReactiveFragmentActivity.ThrownExceptions.get -> System.IObservable! ReactiveUI.AndroidX.ReactiveFragmentActivity diff --git a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs index 6c77d401db..e0e0cb1e38 100644 --- a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs +++ b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs @@ -145,81 +145,4 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - - /// - /// Awaits the first that matches a request code, then completes the task and - /// detaches. A fused, allocation-light replacement for Where(...).Select(...).FirstAsync().ToTask(): - /// it is its own observer, settles exactly once, and unsubscribes on completion. - /// - private sealed class ActivityResultAwaiter - : IObserver<(int requestCode, Result result, Intent? intent)>, IDisposable - { - /// The request code this awaiter is waiting for. - private readonly int _requestCode; - - /// Completion source backing the returned task. - private readonly TaskCompletionSource<(Result result, Intent? intent)> _completion = new(); - - /// Holds the source subscription so it can be torn down once settled. - private readonly OnceDisposable _subscription = new(); - - /// Guards against settling more than once. - private int _settled; - - /// Initializes a new instance of the class. - /// The request code to await. - private ActivityResultAwaiter(int requestCode) => _requestCode = requestCode; - - /// Subscribes to the activity-result stream and returns a task for the first matching result. - /// The activity-result stream. - /// The request code to await. - /// A task that completes with the result and intent of the first matching activity result. - public static Task<(Result result, Intent? intent)> Await( - IObservable<(int requestCode, Result result, Intent? intent)> source, - int requestCode) - { - var awaiter = new ActivityResultAwaiter(requestCode); - awaiter._subscription.Disposable = source.Subscribe(awaiter); - return awaiter._completion.Task; - } - - /// - public void OnNext((int requestCode, Result result, Intent? intent) value) - { - if (value.requestCode != _requestCode || Interlocked.Exchange(ref _settled, 1) != 0) - { - return; - } - - _completion.TrySetResult((value.result, value.intent)); - Dispose(); - } - - /// - public void OnError(Exception error) - { - if (Interlocked.Exchange(ref _settled, 1) != 0) - { - return; - } - - _completion.TrySetException(error); - Dispose(); - } - - /// - public void OnCompleted() - { - if (Interlocked.Exchange(ref _settled, 1) != 0) - { - return; - } - - _completion.TrySetCanceled(); - Dispose(); - } - - /// - public void Dispose() => _subscription.Dispose(); - } } diff --git a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs index 175836dbcd..3e412e6ab5 100644 --- a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs +++ b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs @@ -23,7 +23,7 @@ public class ReactiveFragmentActivity : FragmentActivity, IReactiveObject, private readonly Signal _deactivated = new(); /// The subject that signals activity results. - private readonly Signal<(int requestCode, Result result, Intent intent)> _activityResult = new(); + private readonly Signal<(int requestCode, Result result, Intent? intent)> _activityResult = new(); /// public event PropertyChangingEventHandler? PropertyChanging; @@ -49,7 +49,7 @@ public class ReactiveFragmentActivity : FragmentActivity, IReactiveObject, public IObservable Deactivated => _deactivated; /// Gets the activity result. - public IObservable<(int requestCode, Result result, Intent intent)> ActivityResult => + public IObservable<(int requestCode, Result result, Intent? intent)> ActivityResult => _activityResult; /// @@ -65,7 +65,7 @@ public class ReactiveFragmentActivity : FragmentActivity, IReactiveObject, /// The intent. /// The request code. /// A task with the result and intent. - public Task<(Result result, Intent intent)> StartActivityForResultAsync(Intent intent, int requestCode) + public Task<(Result result, Intent? intent)> StartActivityForResultAsync(Intent intent, int requestCode) { // NB: It's important that we set up the subscription *before* we // call ActivityForResult @@ -79,7 +79,7 @@ public class ReactiveFragmentActivity : FragmentActivity, IReactiveObject, /// The type. /// The request code. /// A task with the result and intent. - public Task<(Result result, Intent intent)> StartActivityForResultAsync(Type type, int requestCode) + public Task<(Result result, Intent? intent)> StartActivityForResultAsync(Type type, int requestCode) { // NB: It's important that we set up the subscription *before* we // call ActivityForResult @@ -106,8 +106,6 @@ protected override void OnResume() /// protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data) { - ArgumentExceptionHelper.ThrowIfNull(data); - base.OnActivityResult(requestCode, resultCode, data); _activityResult.OnNext((requestCode, resultCode, data)); } @@ -124,81 +122,4 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - - /// - /// Awaits the first that matches a request code, then completes the task and - /// detaches. A fused, allocation-light replacement for Where(...).Select(...).FirstAsync().ToTask(): - /// it is its own observer, settles exactly once, and unsubscribes on completion. - /// - private sealed class ActivityResultAwaiter - : IObserver<(int requestCode, Result result, Intent intent)>, IDisposable - { - /// The request code this awaiter is waiting for. - private readonly int _requestCode; - - /// Completion source backing the returned task. - private readonly TaskCompletionSource<(Result result, Intent intent)> _completion = new(); - - /// Holds the source subscription so it can be torn down once settled. - private readonly OnceDisposable _subscription = new(); - - /// Guards against settling more than once. - private int _settled; - - /// Initializes a new instance of the class. - /// The request code to await. - private ActivityResultAwaiter(int requestCode) => _requestCode = requestCode; - - /// Subscribes to the activity-result stream and returns a task for the first matching result. - /// The activity-result stream. - /// The request code to await. - /// A task that completes with the result and intent of the first matching activity result. - public static Task<(Result result, Intent intent)> Await( - IObservable<(int requestCode, Result result, Intent intent)> source, - int requestCode) - { - var awaiter = new ActivityResultAwaiter(requestCode); - awaiter._subscription.Disposable = source.Subscribe(awaiter); - return awaiter._completion.Task; - } - - /// - public void OnNext((int requestCode, Result result, Intent intent) value) - { - if (value.requestCode != _requestCode || Interlocked.Exchange(ref _settled, 1) != 0) - { - return; - } - - _completion.TrySetResult((value.result, value.intent)); - Dispose(); - } - - /// - public void OnError(Exception error) - { - if (Interlocked.Exchange(ref _settled, 1) != 0) - { - return; - } - - _completion.TrySetException(error); - Dispose(); - } - - /// - public void OnCompleted() - { - if (Interlocked.Exchange(ref _settled, 1) != 0) - { - return; - } - - _completion.TrySetCanceled(); - Dispose(); - } - - /// - public void Dispose() => _subscription.Dispose(); - } } diff --git a/src/ReactiveUI.Blazor/Internal/ReactiveComponentHelpers.cs b/src/ReactiveUI.Blazor/Internal/ReactiveComponentHelpers.cs index 3e65d322e0..73d7c7f702 100644 --- a/src/ReactiveUI.Blazor/Internal/ReactiveComponentHelpers.cs +++ b/src/ReactiveUI.Blazor/Internal/ReactiveComponentHelpers.cs @@ -52,12 +52,12 @@ public static void WireActivationIfSupported(T? viewModel, ReactiveComponentS } // Subscribe to component activation and trigger view model activation. - state.Activated + _ = state.Activated .Subscribe(new DelegateObserver(_ => avm.Activator.Activate())) .DisposeWith(state.LifetimeDisposables); // Deactivation subscription does not need disposal tracking beyond component lifetime. - state.Deactivated.Subscribe(new DelegateObserver(_ => avm.Activator.Deactivate())); + _ = state.Deactivated.Subscribe(new DelegateObserver(_ => avm.Activator.Deactivate())); } /// diff --git a/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs b/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs index a5b2827ca0..a789e2025f 100644 --- a/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs +++ b/src/ReactiveUI.Blend/FollowObservableStateBehavior.cs @@ -96,11 +96,11 @@ protected static void OnStateObservableChanged(DependencyObject? sender, Depende var target = item.TargetObject ?? item.AssociatedObject; if (target is Control) { - VisualStateManager.GoToState(target, x, true); + _ = VisualStateManager.GoToState(target, x, true); } else { - VisualStateManager.GoToElementState(target, x, true); + _ = VisualStateManager.GoToElementState(target, x, true); } }, _ => diff --git a/src/ReactiveUI.Core/Bindings/Command/CreatesCommandBinding.cs b/src/ReactiveUI.Core/Bindings/Command/CreatesCommandBinding.cs index 8764e38a9f..71f7ba146b 100644 --- a/src/ReactiveUI.Core/Bindings/Command/CreatesCommandBinding.cs +++ b/src/ReactiveUI.Core/Bindings/Command/CreatesCommandBinding.cs @@ -92,12 +92,17 @@ private static ICreatesCommandBinding GetBinder< DynamicallyAccessedMemberTypes.PublicProperties)] T>(bool hasEventTarget) { - var binder = AppLocator.Current.GetServices() - .Aggregate((score: 0, binding: (ICreatesCommandBinding?)null), (acc, x) => + var bestScore = 0; + ICreatesCommandBinding? binder = null; + foreach (var candidate in AppLocator.Current.GetServices()) + { + var score = candidate.GetAffinityForObject(hasEventTarget); + if (score > bestScore) { - var score = x.GetAffinityForObject(hasEventTarget); - return score > acc.score ? (score, x) : acc; - }).binding; + bestScore = score; + binder = candidate; + } + } return binder ?? throw new InvalidOperationException($"Couldn't find a Command Binder for {typeof(T).FullName}"); } diff --git a/src/ReactiveUI.Core/Bindings/Converter/ComponentModelConversion.cs b/src/ReactiveUI.Core/Bindings/Converter/ComponentModelConversion.cs index 48158f9e1b..40164548f3 100644 --- a/src/ReactiveUI.Core/Bindings/Converter/ComponentModelConversion.cs +++ b/src/ReactiveUI.Core/Bindings/Converter/ComponentModelConversion.cs @@ -53,12 +53,9 @@ static bool CapabilityFactory((Type From, Type To) key) var fromIsString = key.From == typeof(string); var (lookupFrom, lookupTo) = fromIsString ? (key.To, key.From) : (key.From, key.To); var converter = TypeDescriptor.GetConverter(lookupFrom); - if (fromIsString) - { - return converter?.CanConvertFrom(typeof(string)) == true; - } - - return converter?.CanConvertTo(lookupTo) == true; + return fromIsString + ? converter?.CanConvertFrom(typeof(string)) == true + : converter?.CanConvertTo(lookupTo) == true; } catch { diff --git a/src/ReactiveUI.Core/Bindings/Converters/BindingFallbackConverterRegistry.cs b/src/ReactiveUI.Core/Bindings/Converters/BindingFallbackConverterRegistry.cs index 0f46338b3a..4d55b9c8fb 100644 --- a/src/ReactiveUI.Core/Bindings/Converters/BindingFallbackConverterRegistry.cs +++ b/src/ReactiveUI.Core/Bindings/Converters/BindingFallbackConverterRegistry.cs @@ -146,12 +146,7 @@ public void Register(IBindingFallbackConverter converter) public IEnumerable GetAllConverters() { var snap = Volatile.Read(ref _snapshot); - if (snap is null) - { - return []; - } - - return [.. snap.Converters]; + return snap is null ? [] : [.. snap.Converters]; } /// A copy-on-write snapshot of the fallback converter registry. diff --git a/src/ReactiveUI.Core/Bindings/Converters/ConverterMigrationHelperMixins.cs b/src/ReactiveUI.Core/Bindings/Converters/ConverterMigrationHelperMixins.cs index 62732ec52e..504d446c2d 100644 --- a/src/ReactiveUI.Core/Bindings/Converters/ConverterMigrationHelperMixins.cs +++ b/src/ReactiveUI.Core/Bindings/Converters/ConverterMigrationHelperMixins.cs @@ -113,14 +113,32 @@ public static ( { ArgumentExceptionHelper.ThrowIfNull(resolver); - List typed = - [.. resolver.GetServices().Where(static c => c is not null)]; + List typed = []; + foreach (var converter in resolver.GetServices()) + { + if (converter is not null) + { + typed.Add(converter); + } + } - List fallback = - [.. resolver.GetServices().Where(static c => c is not null)]; + List fallback = []; + foreach (var converter in resolver.GetServices()) + { + if (converter is not null) + { + fallback.Add(converter); + } + } - List setMethod = - [.. resolver.GetServices().Where(static c => c is not null)]; + List setMethod = []; + foreach (var converter in resolver.GetServices()) + { + if (converter is not null) + { + setMethod.Add(converter); + } + } return (typed, fallback, setMethod); } diff --git a/src/ReactiveUI.Core/Bindings/Converters/SetMethodBindingConverterRegistry.cs b/src/ReactiveUI.Core/Bindings/Converters/SetMethodBindingConverterRegistry.cs index 0b3e7bd74d..c26bdb8959 100644 --- a/src/ReactiveUI.Core/Bindings/Converters/SetMethodBindingConverterRegistry.cs +++ b/src/ReactiveUI.Core/Bindings/Converters/SetMethodBindingConverterRegistry.cs @@ -144,12 +144,7 @@ public void Register(ISetMethodBindingConverter converter) public IEnumerable GetAllConverters() { var snap = Volatile.Read(ref _snapshot); - if (snap is null) - { - return []; - } - - return [.. snap.Converters]; + return snap is null ? [] : [.. snap.Converters]; } /// A copy-on-write snapshot of the set-method converter registry. diff --git a/src/ReactiveUI.Core/Bindings/Property/Internal/BindingConverterResolver.cs b/src/ReactiveUI.Core/Bindings/Property/Internal/BindingConverterResolver.cs index dec5e7a6ca..36a627f1c4 100644 --- a/src/ReactiveUI.Core/Bindings/Property/Internal/BindingConverterResolver.cs +++ b/src/ReactiveUI.Core/Bindings/Property/Internal/BindingConverterResolver.cs @@ -28,27 +28,19 @@ private static readonly ResolveBestConverter(fromType, toType); /// - public Func? GetSetMethodConverter(Type? fromType, Type? toType) - { - if (fromType is null) - { - return null; - } - - return _setMethodCache.GetOrAdd( - (fromType, toType), - static key => - { - var converter = ResolveBestSetMethodConverter(key.fromType, key.toType); - if (converter is null) + public Func? GetSetMethodConverter(Type? fromType, Type? toType) => + fromType is null + ? null + : _setMethodCache.GetOrAdd( + (fromType, toType), + static key => { - return null; - } - - return (currentValue, newValue, indexParameters) => - converter.PerformSet(currentValue, newValue, indexParameters); - }); - } + var converter = ResolveBestSetMethodConverter(key.fromType, key.toType); + return converter is null + ? null + : (currentValue, newValue, indexParameters) => + converter.PerformSet(currentValue, newValue, indexParameters); + }); /// Resolves the best converter for a given type pair using the ConverterService. /// The source type. diff --git a/src/ReactiveUI.Core/ChangeSets/ChangeSetExtensions.cs b/src/ReactiveUI.Core/ChangeSets/ChangeSetExtensions.cs index a8000db549..a7f4e1d863 100644 --- a/src/ReactiveUI.Core/ChangeSets/ChangeSetExtensions.cs +++ b/src/ReactiveUI.Core/ChangeSets/ChangeSetExtensions.cs @@ -178,15 +178,12 @@ private int GetChangeCapacity(NotifyCollectionChangedEventArgs e) /// The change list being built. private void ApplyAdd(NotifyCollectionChangedEventArgs e, List> changes) { - if (e.NewItems is null) - { - return; - } - + // NewItems is always populated for an Add by the sealed NotifyCollectionChangedEventArgs ctors. + var newItems = e.NewItems!; var start = e.NewStartingIndex; - for (var i = 0; i < e.NewItems.Count; i++) + for (var i = 0; i < newItems.Count; i++) { - var item = (T)e.NewItems[i]!; + var item = (T)newItems[i]!; var index = start < 0 ? _shadow.Count : start + i; _shadow.Insert(index, item); changes.Add(new(ReactiveChangeReason.Add, item, default, index, -1)); @@ -198,15 +195,12 @@ private void ApplyAdd(NotifyCollectionChangedEventArgs e, List /// The change list being built. private void ApplyRemove(NotifyCollectionChangedEventArgs e, List> changes) { - if (e.OldItems is null) - { - return; - } - + // OldItems is always populated for a Remove by the sealed NotifyCollectionChangedEventArgs ctors. + var oldItems = e.OldItems!; var start = e.OldStartingIndex; - for (var i = 0; i < e.OldItems.Count; i++) + for (var i = 0; i < oldItems.Count; i++) { - var item = (T)e.OldItems[i]!; + var item = (T)oldItems[i]!; var index = start < 0 ? -1 : start; RemoveFromShadow(index, item); changes.Add(new(ReactiveChangeReason.Remove, item, default, index, -1)); @@ -218,16 +212,14 @@ private void ApplyRemove(NotifyCollectionChangedEventArgs e, ListThe change list being built. private void ApplyReplace(NotifyCollectionChangedEventArgs e, List> changes) { - if (e.NewItems is null || e.OldItems is null) - { - return; - } - + // Both item lists are always populated for a Replace by the sealed NotifyCollectionChangedEventArgs ctors. + var newItems = e.NewItems!; + var oldItems = e.OldItems!; var start = e.NewStartingIndex; - for (var i = 0; i < e.NewItems.Count; i++) + for (var i = 0; i < newItems.Count; i++) { - var current = (T)e.NewItems[i]!; - var previous = (T)e.OldItems[i]!; + var current = (T)newItems[i]!; + var previous = (T)oldItems[i]!; var index = start < 0 ? -1 : start + i; if (index >= 0 && index < _shadow.Count) { @@ -243,12 +235,8 @@ private void ApplyReplace(NotifyCollectionChangedEventArgs e, ListThe change list being built. private void ApplyMove(NotifyCollectionChangedEventArgs e, List> changes) { - if (e.NewItems is null) - { - return; - } - - var item = (T)e.NewItems[0]!; + // NewItems is always populated for a Move by the sealed NotifyCollectionChangedEventArgs ctors. + var item = (T)e.NewItems![0]!; if (e.OldStartingIndex >= 0 && e.OldStartingIndex < _shadow.Count) { _shadow.RemoveAt(e.OldStartingIndex); @@ -292,7 +280,7 @@ private void RemoveFromShadow(int index, T item) return; } - _shadow.Remove(item); + _ = _shadow.Remove(item); } } } diff --git a/src/ReactiveUI.Core/Expression/ExpressionRewriter.cs b/src/ReactiveUI.Core/Expression/ExpressionRewriter.cs index cd90d0d816..0cccb475b8 100644 --- a/src/ReactiveUI.Core/Expression/ExpressionRewriter.cs +++ b/src/ReactiveUI.Core/Expression/ExpressionRewriter.cs @@ -88,12 +88,9 @@ protected override Expression VisitBinary(BinaryExpression node) var instance = Visit(node.Left); var index = (ConstantExpression)Visit(node.Right); - if (instance.Type.IsArray) - { - return Expression.ArrayAccess(instance, index); - } - - return Expression.MakeIndex(instance, GetItemProperty(instance.Type), [index]); + return instance.Type.IsArray + ? Expression.ArrayAccess(instance, index) + : Expression.MakeIndex(instance, GetItemProperty(instance.Type), [index]); } /// Visits a node and rewrites it as needed for expression tree processing. @@ -204,7 +201,7 @@ protected override Expression VisitIndex(IndexExpression node) private static NotSupportedException CreateUnsupportedNodeException(Expression node) { StringBuilder sb = new(96); - sb.Append("Unsupported expression of type '") + _ = sb.Append("Unsupported expression of type '") .Append(node.NodeType) .Append("' ") .Append(node) @@ -212,7 +209,7 @@ private static NotSupportedException CreateUnsupportedNodeException(Expression n if (node is BinaryExpression be) { - sb.Append(" Did you meant to use expressions '") + _ = sb.Append(" Did you meant to use expressions '") .Append(be.Left) .Append("' and '") .Append(be.Right) @@ -267,7 +264,7 @@ private static bool AllConstant(ReadOnlyCollection expressions) /// Visits a method argument list without LINQ allocations. /// The argument list to visit. - /// A visited argument array suitable for . + /// A visited argument array suitable for . private Expression[] VisitArgumentList(ReadOnlyCollection arguments) { var count = arguments.Count; diff --git a/src/ReactiveUI.Core/Internal/TaskObservable.cs b/src/ReactiveUI.Core/Internal/TaskObservable.cs index 6f7db9471d..05c5ce56da 100644 --- a/src/ReactiveUI.Core/Internal/TaskObservable.cs +++ b/src/ReactiveUI.Core/Internal/TaskObservable.cs @@ -19,7 +19,7 @@ public sealed class TaskObservable(Task task) : IObservable public IDisposable Subscribe(IObserver observer) { ArgumentExceptionHelper.ThrowIfNull(observer); - task.ContinueWith( + _ = task.ContinueWith( static (completed, state) => { var observer = (IObserver)state!; diff --git a/src/ReactiveUI.Core/Mixins/CompatMixins.cs b/src/ReactiveUI.Core/Mixins/CompatMixins.cs deleted file mode 100644 index cb7c082a42..0000000000 --- a/src/ReactiveUI.Core/Mixins/CompatMixins.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -namespace ReactiveUI; - -/// Provides extension methods for compatibility with collection operations. -/// This class contains internal extension methods intended to supplement collection handling -/// functionality. These methods are not intended for public use and may be subject to change or removal without -/// notice. -internal static class CompatMixins -{ - /// Provides ForEach extension members for . - /// The type of the elements in the enumerable collection. - /// The enumerable collection whose elements the action is performed on. Cannot be null. - extension(IEnumerable @this) - { - /// Performs the specified action on each element of the enumerable collection. - /// This method is intended for scenarios where side effects are required for each element in the - /// collection. It does not modify the collection or return a result. - /// The action to perform on each element of the collection. Cannot be null. - internal void ForEach(Action block) - { - foreach (var v in @this) - { - block(v); - } - } - - /// - /// Returns a new sequence that contains the elements of the input sequence except for the specified number of - /// elements at the end. - /// - /// The number of elements to omit from the end of the sequence. Must be non-negative. - /// An IEnumerable{T} that contains the elements of the input sequence except for the specified number at the end. - /// If count is greater than or equal to the number of elements in the sequence, an empty sequence is returned. - internal IEnumerable SkipLast(int count) - { - var inputList = @this.ToList(); - return inputList.Take(inputList.Count - count); - } - } -} diff --git a/src/ReactiveUI.Core/Mixins/DependencyResolverMixins.cs b/src/ReactiveUI.Core/Mixins/DependencyResolverMixins.cs index 530c1116df..17d3807ab5 100644 --- a/src/ReactiveUI.Core/Mixins/DependencyResolverMixins.cs +++ b/src/ReactiveUI.Core/Mixins/DependencyResolverMixins.cs @@ -30,18 +30,14 @@ public void RegisterViewsForViewModels(Assembly assembly) ArgumentExceptionHelper.ThrowIfNull(resolver); ArgumentExceptionHelper.ThrowIfNull(assembly); - foreach (var ti in assembly.DefinedTypes - .Where(static ti => ti.ImplementedInterfaces.Contains(typeof(IViewFor)) && !ti.IsAbstract)) + foreach (var ti in assembly.DefinedTypes) { - if (ti.GetCustomAttribute() is not null) + if (ti.IsAbstract || ti.GetCustomAttribute() is not null) { continue; } - var ivf = ti.ImplementedInterfaces.FirstOrDefault(static t => - t.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IViewFor))); - - if (ivf is null) + if (!TryResolveViewForInterface(ti, out var ivf)) { continue; } @@ -54,6 +50,34 @@ public void RegisterViewsForViewModels(Assembly assembly) } } + /// Creates a factory delegate that instantiates objects of the specified type using a public parameterless constructor. + /// The type metadata for which to create the factory. The type must have a public parameterless constructor. + /// A delegate that creates a new instance of the specified type when invoked. + /// Thrown if the specified type does not have a public parameterless constructor, or if instantiation fails. + /// Internal so the missing-parameterless-constructor guard can be exercised directly in tests. + internal static Func TypeFactory( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + TypeInfo typeInfo) + { + ConstructorInfo? parameterlessConstructor = null; + foreach (var ci in typeInfo.DeclaredConstructors) + { + if (ci.IsPublic && ci.GetParameters().Length == 0) + { + parameterlessConstructor = ci; + break; + } + } + + return parameterlessConstructor is null + ? throw new InvalidOperationException( + $"Failed to register type {typeInfo.FullName} because it's missing a parameterless constructor.") + : () => Activator.CreateInstance(typeInfo.AsType()) + ?? throw new InvalidOperationException( + $"Failed to instantiate type {typeInfo.FullName} - ensure it has a public parameterless constructor."); + } + /// /// Registers a type with the specified dependency resolver, using singleton or transient lifetime based on the /// type's attributes and an optional contract. @@ -108,22 +132,49 @@ private static void RegisterType( } } - /// Creates a factory delegate that instantiates objects of the specified type using a public parameterless constructor. - /// The type metadata for which to create the factory. The type must have a public parameterless constructor. - /// A delegate that creates a new instance of the specified type when invoked. - /// Thrown if the specified type does not have a public parameterless constructor, or if instantiation fails. - private static Func TypeFactory( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | - DynamicallyAccessedMemberTypes.NonPublicConstructors)] - TypeInfo typeInfo) + /// + /// In a single pass over the implemented interfaces, confirms the type derives from + /// and captures the concrete IViewFor<T> interface to register against. + /// + /// The candidate type's metadata. + /// The resolved IViewFor<T> interface when the type is registrable. + /// when the type is a non-marker view that can be registered. + [RequiresUnreferencedCode("Inspects implemented interfaces via reflection.")] + private static bool TryResolveViewForInterface(TypeInfo ti, [NotNullWhen(true)] out Type? viewForInterface) { - var parameterlessConstructor = - typeInfo.DeclaredConstructors.FirstOrDefault(ci => ci.IsPublic && ci.GetParameters().Length == 0); - return parameterlessConstructor is null - ? throw new InvalidOperationException( - $"Failed to register type {typeInfo.FullName} because it's missing a parameterless constructor.") - : () => Activator.CreateInstance(typeInfo.AsType()) - ?? throw new InvalidOperationException( - $"Failed to instantiate type {typeInfo.FullName} - ensure it has a public parameterless constructor."); + var implementsViewFor = false; + viewForInterface = null; + foreach (var iface in ti.ImplementedInterfaces) + { + if (iface == typeof(IViewFor)) + { + implementsViewFor = true; + continue; + } + + if (viewForInterface is null && ImplementsInterface(iface.GetTypeInfo().ImplementedInterfaces, typeof(IViewFor))) + { + viewForInterface = iface; + } + } + + return implementsViewFor && viewForInterface is not null; + } + + /// Determines whether a sequence of interface types contains the target interface. + /// The implemented interface types to search. + /// The interface type to look for. + /// when the target interface is present; otherwise . + private static bool ImplementsInterface(IEnumerable interfaces, Type target) + { + foreach (var iface in interfaces) + { + if (iface == target) + { + return true; + } + } + + return false; } } diff --git a/src/ReactiveUI.Core/Mixins/ExpressionMixins.cs b/src/ReactiveUI.Core/Mixins/ExpressionMixins.cs index 3666136871..0fd29711e2 100644 --- a/src/ReactiveUI.Core/Mixins/ExpressionMixins.cs +++ b/src/ReactiveUI.Core/Mixins/ExpressionMixins.cs @@ -30,24 +30,14 @@ public IEnumerable GetExpressionChain() { case ExpressionType.Index when node is IndexExpression indexExpression: { - var parent = indexExpression.GetParent(); - expressions.Add( - indexExpression.Object is not null && - parent is not null && - indexExpression.Object.NodeType != ExpressionType.Parameter - ? indexExpression.Update(Expression.Parameter(parent.Type), indexExpression.Arguments) - : indexExpression); - + expressions.Add(NormalizeIndexExpression(indexExpression)); node = indexExpression.Object; break; } case ExpressionType.MemberAccess when node is MemberExpression memberExpression: { - var parent = memberExpression.GetParent(); - expressions.Add(parent is not null && memberExpression.Expression is not null && - memberExpression.Expression.NodeType != ExpressionType.Parameter ? memberExpression.Update(Expression.Parameter(parent.Type)) : memberExpression); - + expressions.Add(NormalizeMemberExpression(memberExpression)); node = memberExpression.Expression; break; } @@ -58,7 +48,7 @@ parent is not null && if (node is ConstantExpression) { - errorMessageBuilder.Append(" Did you miss the member access prefix in the expression?"); + _ = errorMessageBuilder.Append(" Did you miss the member access prefix in the expression?"); } throw new NotSupportedException(errorMessageBuilder.ToString()); @@ -118,90 +108,7 @@ parent is not null && ExpressionType.Index when expression is IndexExpression indexExpression => indexExpression.Object, ExpressionType.MemberAccess when expression is MemberExpression memberExpression => memberExpression .Expression, - ExpressionType.Add => throw new NotSupportedException("Resolving the parent of an expression of type 'Add' is not supported by ReactiveUI."), - ExpressionType.AddChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'AddChecked' is not supported by ReactiveUI."), - ExpressionType.And => throw new NotSupportedException("Resolving the parent of an expression of type 'And' is not supported by ReactiveUI."), - ExpressionType.AndAlso => throw new NotSupportedException("Resolving the parent of an expression of type 'AndAlso' is not supported by ReactiveUI."), - ExpressionType.ArrayLength => throw new NotSupportedException("Resolving the parent of an expression of type 'ArrayLength' is not supported by ReactiveUI."), - ExpressionType.ArrayIndex => throw new NotSupportedException("Resolving the parent of an expression of type 'ArrayIndex' is not supported by ReactiveUI."), - ExpressionType.Call => throw new NotSupportedException("Resolving the parent of an expression of type 'Call' is not supported by ReactiveUI."), - ExpressionType.Coalesce => throw new NotSupportedException("Resolving the parent of an expression of type 'Coalesce' is not supported by ReactiveUI."), - ExpressionType.Conditional => throw new NotSupportedException("Resolving the parent of an expression of type 'Conditional' is not supported by ReactiveUI."), - ExpressionType.Constant => throw new NotSupportedException("Resolving the parent of an expression of type 'Constant' is not supported by ReactiveUI."), - ExpressionType.Convert => throw new NotSupportedException("Resolving the parent of an expression of type 'Convert' is not supported by ReactiveUI."), - ExpressionType.ConvertChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'ConvertChecked' is not supported by ReactiveUI."), - ExpressionType.Divide => throw new NotSupportedException("Resolving the parent of an expression of type 'Divide' is not supported by ReactiveUI."), - ExpressionType.Equal => throw new NotSupportedException("Resolving the parent of an expression of type 'Equal' is not supported by ReactiveUI."), - ExpressionType.ExclusiveOr => throw new NotSupportedException("Resolving the parent of an expression of type 'ExclusiveOr' is not supported by ReactiveUI."), - ExpressionType.GreaterThan => throw new NotSupportedException("Resolving the parent of an expression of type 'GreaterThan' is not supported by ReactiveUI."), - ExpressionType.GreaterThanOrEqual => throw new NotSupportedException("Resolving the parent of an expression of type 'GreaterThanOrEqual' is not supported by ReactiveUI."), - ExpressionType.Invoke => throw new NotSupportedException("Resolving the parent of an expression of type 'Invoke' is not supported by ReactiveUI."), - ExpressionType.Lambda => throw new NotSupportedException("Resolving the parent of an expression of type 'Lambda' is not supported by ReactiveUI."), - ExpressionType.LeftShift => throw new NotSupportedException("Resolving the parent of an expression of type 'LeftShift' is not supported by ReactiveUI."), - ExpressionType.LessThan => throw new NotSupportedException("Resolving the parent of an expression of type 'LessThan' is not supported by ReactiveUI."), - ExpressionType.LessThanOrEqual => throw new NotSupportedException("Resolving the parent of an expression of type 'LessThanOrEqual' is not supported by ReactiveUI."), - ExpressionType.ListInit => throw new NotSupportedException("Resolving the parent of an expression of type 'ListInit' is not supported by ReactiveUI."), - ExpressionType.MemberInit => throw new NotSupportedException("Resolving the parent of an expression of type 'MemberInit' is not supported by ReactiveUI."), - ExpressionType.Modulo => throw new NotSupportedException("Resolving the parent of an expression of type 'Modulo' is not supported by ReactiveUI."), - ExpressionType.Multiply => throw new NotSupportedException("Resolving the parent of an expression of type 'Multiply' is not supported by ReactiveUI."), - ExpressionType.MultiplyChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'MultiplyChecked' is not supported by ReactiveUI."), - ExpressionType.Negate => throw new NotSupportedException("Resolving the parent of an expression of type 'Negate' is not supported by ReactiveUI."), - ExpressionType.UnaryPlus => throw new NotSupportedException("Resolving the parent of an expression of type 'UnaryPlus' is not supported by ReactiveUI."), - ExpressionType.NegateChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'NegateChecked' is not supported by ReactiveUI."), - ExpressionType.New => throw new NotSupportedException("Resolving the parent of an expression of type 'New' is not supported by ReactiveUI."), - ExpressionType.NewArrayInit => throw new NotSupportedException("Resolving the parent of an expression of type 'NewArrayInit' is not supported by ReactiveUI."), - ExpressionType.NewArrayBounds => throw new NotSupportedException("Resolving the parent of an expression of type 'NewArrayBounds' is not supported by ReactiveUI."), - ExpressionType.Not => throw new NotSupportedException("Resolving the parent of an expression of type 'Not' is not supported by ReactiveUI."), - ExpressionType.NotEqual => throw new NotSupportedException("Resolving the parent of an expression of type 'NotEqual' is not supported by ReactiveUI."), - ExpressionType.Or => throw new NotSupportedException("Resolving the parent of an expression of type 'Or' is not supported by ReactiveUI."), - ExpressionType.OrElse => throw new NotSupportedException("Resolving the parent of an expression of type 'OrElse' is not supported by ReactiveUI."), - ExpressionType.Parameter => throw new NotSupportedException("Resolving the parent of an expression of type 'Parameter' is not supported by ReactiveUI."), - ExpressionType.Power => throw new NotSupportedException("Resolving the parent of an expression of type 'Power' is not supported by ReactiveUI."), - ExpressionType.Quote => throw new NotSupportedException("Resolving the parent of an expression of type 'Quote' is not supported by ReactiveUI."), - ExpressionType.RightShift => throw new NotSupportedException("Resolving the parent of an expression of type 'RightShift' is not supported by ReactiveUI."), - ExpressionType.Subtract => throw new NotSupportedException("Resolving the parent of an expression of type 'Subtract' is not supported by ReactiveUI."), - ExpressionType.SubtractChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'SubtractChecked' is not supported by ReactiveUI."), - ExpressionType.TypeAs => throw new NotSupportedException("Resolving the parent of an expression of type 'TypeAs' is not supported by ReactiveUI."), - ExpressionType.TypeIs => throw new NotSupportedException("Resolving the parent of an expression of type 'TypeIs' is not supported by ReactiveUI."), - ExpressionType.Assign => throw new NotSupportedException("Resolving the parent of an expression of type 'Assign' is not supported by ReactiveUI."), - ExpressionType.Block => throw new NotSupportedException("Resolving the parent of an expression of type 'Block' is not supported by ReactiveUI."), - ExpressionType.DebugInfo => throw new NotSupportedException("Resolving the parent of an expression of type 'DebugInfo' is not supported by ReactiveUI."), - ExpressionType.Decrement => throw new NotSupportedException("Resolving the parent of an expression of type 'Decrement' is not supported by ReactiveUI."), - ExpressionType.Dynamic => throw new NotSupportedException("Resolving the parent of an expression of type 'Dynamic' is not supported by ReactiveUI."), - ExpressionType.Default => throw new NotSupportedException("Resolving the parent of an expression of type 'Default' is not supported by ReactiveUI."), - ExpressionType.Extension => throw new NotSupportedException("Resolving the parent of an expression of type 'Extension' is not supported by ReactiveUI."), - ExpressionType.Goto => throw new NotSupportedException("Resolving the parent of an expression of type 'Goto' is not supported by ReactiveUI."), - ExpressionType.Increment => throw new NotSupportedException("Resolving the parent of an expression of type 'Increment' is not supported by ReactiveUI."), - ExpressionType.Label => throw new NotSupportedException("Resolving the parent of an expression of type 'Label' is not supported by ReactiveUI."), - ExpressionType.RuntimeVariables => throw new NotSupportedException("Resolving the parent of an expression of type 'RuntimeVariables' is not supported by ReactiveUI."), - ExpressionType.Loop => throw new NotSupportedException("Resolving the parent of an expression of type 'Loop' is not supported by ReactiveUI."), - ExpressionType.Switch => throw new NotSupportedException("Resolving the parent of an expression of type 'Switch' is not supported by ReactiveUI."), - ExpressionType.Throw => throw new NotSupportedException("Resolving the parent of an expression of type 'Throw' is not supported by ReactiveUI."), - ExpressionType.Try => throw new NotSupportedException("Resolving the parent of an expression of type 'Try' is not supported by ReactiveUI."), - ExpressionType.Unbox => throw new NotSupportedException("Resolving the parent of an expression of type 'Unbox' is not supported by ReactiveUI."), - ExpressionType.AddAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'AddAssign' is not supported by ReactiveUI."), - ExpressionType.AndAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'AndAssign' is not supported by ReactiveUI."), - ExpressionType.DivideAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'DivideAssign' is not supported by ReactiveUI."), - ExpressionType.ExclusiveOrAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'ExclusiveOrAssign' is not supported by ReactiveUI."), - ExpressionType.LeftShiftAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'LeftShiftAssign' is not supported by ReactiveUI."), - ExpressionType.ModuloAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'ModuloAssign' is not supported by ReactiveUI."), - ExpressionType.MultiplyAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'MultiplyAssign' is not supported by ReactiveUI."), - ExpressionType.OrAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'OrAssign' is not supported by ReactiveUI."), - ExpressionType.PowerAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'PowerAssign' is not supported by ReactiveUI."), - ExpressionType.RightShiftAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'RightShiftAssign' is not supported by ReactiveUI."), - ExpressionType.SubtractAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'SubtractAssign' is not supported by ReactiveUI."), - ExpressionType.AddAssignChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'AddAssignChecked' is not supported by ReactiveUI."), - ExpressionType.MultiplyAssignChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'MultiplyAssignChecked' is not supported by ReactiveUI."), - ExpressionType.SubtractAssignChecked => throw new NotSupportedException("Resolving the parent of an expression of type 'SubtractAssignChecked' is not supported by ReactiveUI."), - ExpressionType.PreIncrementAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'PreIncrementAssign' is not supported by ReactiveUI."), - ExpressionType.PreDecrementAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'PreDecrementAssign' is not supported by ReactiveUI."), - ExpressionType.PostIncrementAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'PostIncrementAssign' is not supported by ReactiveUI."), - ExpressionType.PostDecrementAssign => throw new NotSupportedException("Resolving the parent of an expression of type 'PostDecrementAssign' is not supported by ReactiveUI."), - ExpressionType.TypeEqual => throw new NotSupportedException("Resolving the parent of an expression of type 'TypeEqual' is not supported by ReactiveUI."), - ExpressionType.OnesComplement => throw new NotSupportedException("Resolving the parent of an expression of type 'OnesComplement' is not supported by ReactiveUI."), - ExpressionType.IsTrue => throw new NotSupportedException("Resolving the parent of an expression of type 'IsTrue' is not supported by ReactiveUI."), - ExpressionType.IsFalse => throw new NotSupportedException("Resolving the parent of an expression of type 'IsFalse' is not supported by ReactiveUI."), - _ => throw new NotSupportedException($"Unsupported expression type: '{expression.NodeType}'") + _ => throw new NotSupportedException($"Resolving the parent of an expression of type '{expression.NodeType}' is not supported by ReactiveUI.") }; } @@ -214,9 +121,41 @@ parent is not null && { ArgumentExceptionHelper.ThrowIfNull(expression); - return expression.NodeType != ExpressionType.Index - ? null - : [.. ((IndexExpression)expression).Arguments.Cast().Select(static c => c.Value)]; + if (expression.NodeType != ExpressionType.Index) + { + return null; + } + + var arguments = ((IndexExpression)expression).Arguments; + var result = new object?[arguments.Count]; + for (var i = 0; i < arguments.Count; i++) + { + result[i] = ((ConstantExpression)arguments[i]).Value; + } + + return result; } } + + /// Rewrites an index expression's receiver to a parameter placeholder when it is a nested member access. + /// The index expression to normalize. + /// The normalized expression to add to the chain. + private static IndexExpression NormalizeIndexExpression(IndexExpression indexExpression) + { + var parent = indexExpression.GetParent(); + return indexExpression.Object is not null && parent is not null && indexExpression.Object.NodeType != ExpressionType.Parameter + ? indexExpression.Update(Expression.Parameter(parent.Type), indexExpression.Arguments) + : indexExpression; + } + + /// Rewrites a member expression's receiver to a parameter placeholder when it is a nested member access. + /// The member expression to normalize. + /// The normalized expression to add to the chain. + private static MemberExpression NormalizeMemberExpression(MemberExpression memberExpression) + { + var parent = memberExpression.GetParent(); + return parent is not null && memberExpression.Expression is not null && memberExpression.Expression.NodeType != ExpressionType.Parameter + ? memberExpression.Update(Expression.Parameter(parent.Type)) + : memberExpression; + } } diff --git a/src/ReactiveUI.Core/ObservableForProperty/INPCObservableForProperty.cs b/src/ReactiveUI.Core/ObservableForProperty/INPCObservableForProperty.cs index dee20b6889..e63649594d 100644 --- a/src/ReactiveUI.Core/ObservableForProperty/INPCObservableForProperty.cs +++ b/src/ReactiveUI.Core/ObservableForProperty/INPCObservableForProperty.cs @@ -76,12 +76,9 @@ public int GetAffinityForObject(Type? type, string propertyName, bool beforeChan return new BeforeChangeNotification(before, sender, expression, expectedName); } - if (sender is INotifyPropertyChanged after) - { - return new ChangeNotification(after, sender, expression, expectedName); - } - - return Signal.Silent>(); + return sender is INotifyPropertyChanged after + ? new ChangeNotification(after, sender, expression, expectedName) + : Signal.Silent>(); } /// Determines whether a notified property name matches the observed property (an empty name means "all properties"). diff --git a/src/ReactiveUI.Core/View/DefaultViewLocator.cs b/src/ReactiveUI.Core/View/DefaultViewLocator.cs index 85ef3bc5d2..0f308620ad 100644 --- a/src/ReactiveUI.Core/View/DefaultViewLocator.cs +++ b/src/ReactiveUI.Core/View/DefaultViewLocator.cs @@ -101,7 +101,7 @@ public DefaultViewLocator Map(Func factory, string? co [key] = factory }; - Interlocked.Exchange(ref _mappings, newMappings); + _ = Interlocked.Exchange(ref _mappings, newMappings); } return this; @@ -140,8 +140,8 @@ public DefaultViewLocator Unmap(string? contract) } Dictionary<(Type, string), Func> newMappings = new(current); - newMappings.Remove(key); - Interlocked.Exchange(ref _mappings, newMappings); + _ = newMappings.Remove(key); + _ = Interlocked.Exchange(ref _mappings, newMappings); } return this; @@ -186,7 +186,7 @@ public DefaultViewLocator Unmap(string? contract) this.Log().Debug( CultureInfo.InvariantCulture, "Resolved IViewFor<{0}> from explicit mapping", - typeof(TViewModel).Name); + nameof(TViewModel)); return (IViewFor)factory(); } @@ -196,14 +196,14 @@ public DefaultViewLocator Unmap(string? contract) this.Log().Debug( CultureInfo.InvariantCulture, "Resolved IViewFor<{0}> via service locator", - typeof(TViewModel).Name); + nameof(TViewModel)); return view; } this.Log().Warn( CultureInfo.InvariantCulture, "Failed to resolve view for {0}. Use Map() or register IViewFor in the service locator.", - typeof(TViewModel).Name); + nameof(TViewModel)); return null; } diff --git a/src/ReactiveUI.Core/View/ViewMappingBuilder.cs b/src/ReactiveUI.Core/View/ViewMappingBuilder.cs index 939ca0c48d..e91dd0cb91 100644 --- a/src/ReactiveUI.Core/View/ViewMappingBuilder.cs +++ b/src/ReactiveUI.Core/View/ViewMappingBuilder.cs @@ -51,7 +51,7 @@ public ViewMappingBuilder Map(string? contract) where TViewModel : class where TView : class, IViewFor, new() { - _locator.Map(() => new(), contract); + _ = _locator.Map(() => new(), contract); return this; } @@ -87,7 +87,7 @@ public ViewMappingBuilder Map(Func factory, string? co where TView : class, IViewFor { ArgumentExceptionHelper.ThrowIfNull(factory); - _locator.Map(factory, contract); + _ = _locator.Map(factory, contract); return this; } @@ -120,9 +120,9 @@ public ViewMappingBuilder MapFromServiceLocator(string? contr where TViewModel : class where TView : class, IViewFor { - _locator.Map( + _ = _locator.Map( () => AppLocator.Current.GetService() ?? - throw new InvalidOperationException($"View {typeof(TView).Name} not registered in service locator"), + throw new InvalidOperationException($"View {nameof(TView)} not registered in service locator"), contract); return this; } diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt index 1a61ece613..ad7e532aa1 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -147,6 +155,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -182,6 +192,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt index e25df13608..1d98f644fd 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt @@ -107,11 +107,19 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void ReactiveUI.Reactive.Maui.ReactiveUserControl ReactiveUI.Reactive.Maui.ReactiveUserControl.BindingRoot.get -> TViewModel? ReactiveUI.Reactive.Maui.ReactiveUserControl.ReactiveUserControl() -> void ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModel.get -> TViewModel? ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -159,6 +167,8 @@ override ReactiveUI.Reactive.Maui.ReactiveMultiPage.OnBinding override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -195,7 +205,9 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModelProperty -> Microsoft.UI.Xaml.DependencyProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net10.0/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt index 1a61ece613..ad7e532aa1 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -147,6 +155,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -182,6 +192,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt index e25df13608..1d98f644fd 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt @@ -107,11 +107,19 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void ReactiveUI.Reactive.Maui.ReactiveUserControl ReactiveUI.Reactive.Maui.ReactiveUserControl.BindingRoot.get -> TViewModel? ReactiveUI.Reactive.Maui.ReactiveUserControl.ReactiveUserControl() -> void ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModel.get -> TViewModel? ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -159,6 +167,8 @@ override ReactiveUI.Reactive.Maui.ReactiveMultiPage.OnBinding override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -195,7 +205,9 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModelProperty -> Microsoft.UI.Xaml.DependencyProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt index e25df13608..1d98f644fd 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt @@ -107,11 +107,19 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void ReactiveUI.Reactive.Maui.ReactiveUserControl ReactiveUI.Reactive.Maui.ReactiveUserControl.BindingRoot.get -> TViewModel? ReactiveUI.Reactive.Maui.ReactiveUserControl.ReactiveUserControl() -> void ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModel.get -> TViewModel? ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -159,6 +167,8 @@ override ReactiveUI.Reactive.Maui.ReactiveMultiPage.OnBinding override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -195,7 +205,9 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveUserControl.ViewModelProperty -> Microsoft.UI.Xaml.DependencyProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0/PublicAPI.Shipped.txt index 110166e943..01f270e247 100644 --- a/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui.Reactive/PublicAPI/net9.0/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Reactive.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Reactive.Maui.ReactiveWindow +ReactiveUI.Reactive.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Reactive.Maui.Registrations ReactiveUI.Reactive.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Reactive.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Reactive.Maui.ReactiveNavigationPage.OnBindingCo override ReactiveUI.Reactive.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Reactive.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Reactive.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.Reactive.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Reactive.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.Reactive.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Reactive.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.Detail static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Reactive.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Reactive.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui.Reactive/ReactiveUI.Maui.Reactive.csproj b/src/ReactiveUI.Maui.Reactive/ReactiveUI.Maui.Reactive.csproj index 6616c8ba88..8cd0b6891b 100644 --- a/src/ReactiveUI.Maui.Reactive/ReactiveUI.Maui.Reactive.csproj +++ b/src/ReactiveUI.Maui.Reactive/ReactiveUI.Maui.Reactive.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/ReactiveUI.Maui/ActivationForViewFetcher.cs b/src/ReactiveUI.Maui/ActivationForViewFetcher.cs index 4d4e213178..f78c35534a 100644 --- a/src/ReactiveUI.Maui/ActivationForViewFetcher.cs +++ b/src/ReactiveUI.Maui/ActivationForViewFetcher.cs @@ -36,17 +36,20 @@ namespace ReactiveUI.Maui; public class ActivationForViewFetcher : IActivationForViewFetcher { /// - public int GetAffinityForView(Type view) => + public int GetAffinityForView(Type view) + { + var typeInfo = view.GetTypeInfo(); + var isActivatableView = #if IS_WINUI - typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) + typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(typeInfo); #endif #if IS_MAUI - typeof(Page).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) || - typeof(View).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) || - typeof(Cell).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) + typeof(Page).GetTypeInfo().IsAssignableFrom(typeInfo) || + typeof(View).GetTypeInfo().IsAssignableFrom(typeInfo) || + typeof(Cell).GetTypeInfo().IsAssignableFrom(typeInfo); #endif - ? BindingAffinity.ExactType - : 0; + return isActivatableView ? BindingAffinity.ExactType : 0; + } /// public IObservable GetActivationForView(IActivatableView view) @@ -73,13 +76,7 @@ public IObservable GetActivationForView(IActivatableView view) /// The activation stream, or null when is null. private static IObservable? GetActivationFor(ICanActivate? canActivate) { - if (canActivate is null) - { - return null; - } - - // Replaces Activated.Select(_ => true).Merge(Deactivated.Select(_ => false)). - return Signal.Blend( + return canActivate is null ? null : Signal.Blend( new MapSignal(canActivate.Activated, static _ => true), new MapSignal(canActivate.Deactivated, static _ => false)); } diff --git a/src/ReactiveUI.Maui/AutoSuspendHelper.cs b/src/ReactiveUI.Maui/AutoSuspendHelper.cs index a128934009..737b9067ee 100644 --- a/src/ReactiveUI.Maui/AutoSuspendHelper.cs +++ b/src/ReactiveUI.Maui/AutoSuspendHelper.cs @@ -4,6 +4,7 @@ // See the LICENSE file in the project root for full license information. using Splat; +using Application = Microsoft.Maui.Controls.Application; #if REACTIVE_SHIM namespace ReactiveUI.Reactive.Maui; @@ -14,7 +15,7 @@ namespace ReactiveUI.Maui; /// Helps manage .NET MAUI application lifecycle events. /// /// -/// Instantiate this class to wire to MAUI's +/// Instantiate this class to wire to MAUI's /// callbacks. The helper propagates OnStart, OnResume, and OnSleep to the suspension host so state /// drivers created via SetupDefaultSuspendResume can serialize view models consistently across Android, iOS, and /// desktop targets. @@ -78,16 +79,16 @@ public AutoSuspendHelper() /// Gets a subject to indicate whether the application has crashed. public static ISignal UntimelyDemise { get; } = new Signal(); - /// Call this method in the constructor of your .NET MAUI . + /// Call this method in the constructor of your .NET MAUI . public void OnCreate() => _onLaunchingNew.OnNext(RxVoid.Default); - /// Call this method in to relay MAUI's start notification. + /// Call this method in to relay MAUI's start notification. public void OnStart() => _onStart.OnNext(RxVoid.Default); - /// Call this method in when the app is going to the background. + /// Call this method in when the app is going to the background. public void OnSleep() => _onSleep.OnNext(EmptyDisposable.Instance); - /// Call this method in when the app returns to the foreground. + /// Call this method in when the app returns to the foreground. public void OnResume() => _onResume.OnNext(RxVoid.Default); /// diff --git a/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs b/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs index 888b58b22a..950180cfe3 100644 --- a/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs +++ b/src/ReactiveUI.Maui/Builder/MauiReactiveUIBuilderExtensions.cs @@ -62,7 +62,7 @@ public IReactiveUIBuilder WithMauiScheduler(IDispatcher? dispatcher) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithTaskPoolScheduler(TaskPoolSequencer.Default); + _ = builder.WithTaskPoolScheduler(TaskPoolSequencer.Default); return builder.WithMainThreadScheduler(ResolveMainThreadScheduler(dispatcher)); } @@ -98,7 +98,7 @@ public MauiAppBuilder UseReactiveUI(Action withReactiveUIBui var reactiveUIBuilder = RxAppBuilder.CreateReactiveUIBuilder(); withReactiveUIBuilder?.Invoke(reactiveUIBuilder); - reactiveUIBuilder.BuildApp(); + _ = reactiveUIBuilder.BuildApp(); return builder; } @@ -110,7 +110,7 @@ public MauiAppBuilder UseReactiveUI(IDispatcher dispatcher) { ArgumentExceptionHelper.ThrowIfNull(builder); - RxAppBuilder.CreateReactiveUIBuilder().WithMaui(dispatcher).BuildApp(); + _ = RxAppBuilder.CreateReactiveUIBuilder().WithMaui(dispatcher).BuildApp(); return builder; } } @@ -125,11 +125,6 @@ private static ISequencer ResolveMainThreadScheduler(IDispatcher? dispatcher) return dispatcher.ToSequencer(); } - if (ModeDetector.InUnitTestRunner()) - { - return Sequencer.CurrentThread; - } - - return MauiMainThreadScheduler; + return ModeDetector.InUnitTestRunner() ? Sequencer.CurrentThread : MauiMainThreadScheduler; } } diff --git a/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs b/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs index 5562d625a6..100f9591c1 100644 --- a/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs +++ b/src/ReactiveUI.Maui/Common/AutoDataTemplateBindingHook.cs @@ -47,7 +47,7 @@ public bool ExecuteHook( ArgumentNullException.ThrowIfNull(getCurrentViewProperties); var viewProperties = getCurrentViewProperties(); - var lastViewProperty = viewProperties.LastOrDefault(); + var lastViewProperty = viewProperties.Length > 0 ? viewProperties[^1] : null; if (lastViewProperty?.Sender is not ItemsControl itemsControl) { diff --git a/src/ReactiveUI.Maui/Common/RoutedViewHost.cs b/src/ReactiveUI.Maui/Common/RoutedViewHost.cs index f92776ac3b..89a490485e 100644 --- a/src/ReactiveUI.Maui/Common/RoutedViewHost.cs +++ b/src/ReactiveUI.Maui/Common/RoutedViewHost.cs @@ -122,7 +122,7 @@ public RoutedViewHost() // NB: The DistinctUntilChanged is useful because most views in // WinRT will end up getting here twice - once for configuring // the RoutedViewHost's ViewModel, and once on load via SizeChanged - viewModelAndContract.DistinctUntilChanged() + _ = viewModelAndContract.DistinctUntilChanged() .Subscribe(new DelegateObserver<(IRoutableViewModel? viewModel, string? contract)>( ResolveViewForViewModel, RxState.DefaultExceptionHandler.OnNext)) diff --git a/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs index 5f4aecae17..227b2fb627 100644 --- a/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs +++ b/src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs @@ -125,7 +125,7 @@ public RoutedViewHost() // NB: The DistinctUntilChanged is useful because most views in // WinRT will end up getting here twice - once for configuring // the RoutedViewHost's ViewModel, and once on load via SizeChanged - viewModelAndContract.DistinctUntilChanged() + _ = viewModelAndContract.DistinctUntilChanged() .Subscribe(new DelegateObserver<(IRoutableViewModel? viewModel, string? contract)>( ResolveViewForViewModel, RxState.DefaultExceptionHandler.OnNext)) @@ -190,7 +190,7 @@ private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? con // Use the generic ResolveView method - this is AOT-safe! var view = viewLocator.ResolveView(x.contract) ?? viewLocator.ResolveView() - ?? throw new InvalidOperationException($"Couldn't find view for '{typeof(TViewModel).Name}'."); + ?? throw new InvalidOperationException($"Couldn't find view for '{nameof(TViewModel)}'."); view.ViewModel = x.viewModel as TViewModel; Content = view; } diff --git a/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs b/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs index 5af032696b..55b6eb7585 100644 --- a/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs +++ b/src/ReactiveUI.Maui/Common/ViewModelViewHost.cs @@ -99,11 +99,11 @@ public ViewModelViewHost() static (contract, vm) => (vm, contract)); // Subscribe directly without WhenActivated - new ObserveOnObservable(ViewContractObservable, RxSchedulers.MainThreadScheduler) + _ = new ObserveOnObservable(ViewContractObservable, RxSchedulers.MainThreadScheduler) .Subscribe(new DelegateObserver(x => _viewContract = x ?? string.Empty)) .DisposeWith(_subscriptions); - viewModelAndContract.DistinctUntilChanged() + _ = viewModelAndContract.DistinctUntilChanged() .Subscribe(new DelegateObserver<(object? ViewModel, string? Contract)>( x => ResolveViewForViewModel(x.ViewModel, x.Contract))) .DisposeWith(_subscriptions); diff --git a/src/ReactiveUI.Maui/Common/ViewModelViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/Common/ViewModelViewHost{TViewModel}.cs index 21f356bd74..1bd01b2f87 100644 --- a/src/ReactiveUI.Maui/Common/ViewModelViewHost{TViewModel}.cs +++ b/src/ReactiveUI.Maui/Common/ViewModelViewHost{TViewModel}.cs @@ -102,11 +102,11 @@ public ViewModelViewHost() static (contract, vm) => (vm, contract)); // Subscribe directly without WhenActivated - new ObserveOnObservable(ViewContractObservable, RxSchedulers.MainThreadScheduler) + _ = new ObserveOnObservable(ViewContractObservable, RxSchedulers.MainThreadScheduler) .Subscribe(new DelegateObserver(x => _viewContract = x ?? string.Empty)) .DisposeWith(_subscriptions); - viewModelAndContract.DistinctUntilChanged() + _ = viewModelAndContract.DistinctUntilChanged() .Subscribe(new DelegateObserver<(TViewModel? ViewModel, string? Contract)>( x => ResolveViewForViewModel(x.ViewModel, x.Contract))) .DisposeWith(_subscriptions); diff --git a/src/ReactiveUI.Maui/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt index 6f928c0e9c..fa4f3ba7e7 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net10.0-android36.0/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -147,6 +155,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -182,6 +192,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net10.0-ios/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net10.0-maccatalyst/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net10.0-macos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net10.0-tvos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt index def7aa829e..3cfb463b72 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net10.0-windows10.0.19041.0/PublicAPI.Shipped.txt @@ -107,11 +107,19 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void ReactiveUI.Maui.ReactiveUserControl ReactiveUI.Maui.ReactiveUserControl.BindingRoot.get -> TViewModel? ReactiveUI.Maui.ReactiveUserControl.ReactiveUserControl() -> void ReactiveUI.Maui.ReactiveUserControl.ViewModel.get -> TViewModel? ReactiveUI.Maui.ReactiveUserControl.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -159,6 +167,8 @@ override ReactiveUI.Maui.ReactiveMultiPage.OnBindingContextCh override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -195,7 +205,9 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveUserControl.ViewModelProperty -> Microsoft.UI.Xaml.DependencyProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net10.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net10.0/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net10.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net10.0/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt index 6f928c0e9c..fa4f3ba7e7 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net11.0-android37/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -147,6 +155,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -182,6 +192,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net11.0-ios/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net11.0-maccatalyst/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net11.0-macos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net11.0-tvos/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt index def7aa829e..3cfb463b72 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net11.0-windows10.0.19041.0/PublicAPI.Shipped.txt @@ -107,11 +107,19 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void ReactiveUI.Maui.ReactiveUserControl ReactiveUI.Maui.ReactiveUserControl.BindingRoot.get -> TViewModel? ReactiveUI.Maui.ReactiveUserControl.ReactiveUserControl() -> void ReactiveUI.Maui.ReactiveUserControl.ViewModel.get -> TViewModel? ReactiveUI.Maui.ReactiveUserControl.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -159,6 +167,8 @@ override ReactiveUI.Maui.ReactiveMultiPage.OnBindingContextCh override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -195,7 +205,9 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveUserControl.ViewModelProperty -> Microsoft.UI.Xaml.DependencyProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt index def7aa829e..3cfb463b72 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net9.0-windows10.0.19041.0/PublicAPI.Shipped.txt @@ -107,11 +107,19 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void ReactiveUI.Maui.ReactiveUserControl ReactiveUI.Maui.ReactiveUserControl.BindingRoot.get -> TViewModel? ReactiveUI.Maui.ReactiveUserControl.ReactiveUserControl() -> void ReactiveUI.Maui.ReactiveUserControl.ViewModel.get -> TViewModel? ReactiveUI.Maui.ReactiveUserControl.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -159,6 +167,8 @@ override ReactiveUI.Maui.ReactiveMultiPage.OnBindingContextCh override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -195,7 +205,9 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveUserControl.ViewModelProperty -> Microsoft.UI.Xaml.DependencyProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/PublicAPI/net9.0/PublicAPI.Shipped.txt b/src/ReactiveUI.Maui/PublicAPI/net9.0/PublicAPI.Shipped.txt index 4eebfea76e..36ba138397 100644 --- a/src/ReactiveUI.Maui/PublicAPI/net9.0/PublicAPI.Shipped.txt +++ b/src/ReactiveUI.Maui/PublicAPI/net9.0/PublicAPI.Shipped.txt @@ -97,6 +97,14 @@ ReactiveUI.Maui.ReactiveTextItemView.Text.get -> string? ReactiveUI.Maui.ReactiveTextItemView.Text.set -> void ReactiveUI.Maui.ReactiveTextItemView.TextColor.get -> Microsoft.Maui.Graphics.Color! ReactiveUI.Maui.ReactiveTextItemView.TextColor.set -> void +ReactiveUI.Maui.ReactiveTitleBar +ReactiveUI.Maui.ReactiveTitleBar.ReactiveTitleBar() -> void +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveTitleBar.ViewModel.set -> void +ReactiveUI.Maui.ReactiveWindow +ReactiveUI.Maui.ReactiveWindow.ReactiveWindow() -> void +ReactiveUI.Maui.ReactiveWindow.ViewModel.get -> TViewModel? +ReactiveUI.Maui.ReactiveWindow.ViewModel.set -> void ReactiveUI.Maui.Registrations ReactiveUI.Maui.Registrations.Register(ReactiveUI.IRegistrar! registrar) -> void ReactiveUI.Maui.Registrations.Registrations() -> void @@ -145,6 +153,8 @@ override ReactiveUI.Maui.ReactiveNavigationPage.OnBindingContextChan override ReactiveUI.Maui.ReactivePage.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveShell.OnBindingContextChanged() -> void override ReactiveUI.Maui.ReactiveTabbedPage.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveTitleBar.OnBindingContextChanged() -> void +override ReactiveUI.Maui.ReactiveWindow.OnBindingContextChanged() -> void override ReactiveUI.Maui.RoutedViewHost.PageForViewModel(ReactiveUI.IRoutableViewModel! vm) -> Microsoft.Maui.Controls.Page! override ReactiveUI.Maui.RoutedViewHost.PagesForViewModel(ReactiveUI.IRoutableViewModel? vm) -> System.IObservable! override ReactiveUI.Maui.ViewModelViewHost.ResolveViewForViewModel(object? viewModel, string? contract) -> void @@ -180,6 +190,8 @@ static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailColorProp static readonly ReactiveUI.Maui.ReactiveTextItemView.DetailProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextColorProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ReactiveTextItemView.TextProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveTitleBar.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! +static readonly ReactiveUI.Maui.ReactiveWindow.ViewModelProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.RouterProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.RoutedViewHost.SetTitleOnNavigateProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly ReactiveUI.Maui.ViewModelViewHost.ContractFallbackByPassProperty -> Microsoft.Maui.Controls.BindableProperty! diff --git a/src/ReactiveUI.Maui/ReactiveShellContent.cs b/src/ReactiveUI.Maui/ReactiveShellContent.cs index ea89cf1f1c..4960738394 100644 --- a/src/ReactiveUI.Maui/ReactiveShellContent.cs +++ b/src/ReactiveUI.Maui/ReactiveShellContent.cs @@ -93,6 +93,6 @@ private void UpdateContentTemplate() return; } - ContentTemplate = new DataTemplate(() => view); + ContentTemplate = new(() => view); } } diff --git a/src/ReactiveUI.Maui/ReactiveTitleBar.cs b/src/ReactiveUI.Maui/ReactiveTitleBar.cs new file mode 100644 index 0000000000..95b6960aa4 --- /dev/null +++ b/src/ReactiveUI.Maui/ReactiveTitleBar.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.Maui.Controls; + +#if REACTIVE_SHIM +namespace ReactiveUI.Reactive.Maui; +#else +namespace ReactiveUI.Maui; +#endif + +/// This is a that is also an . +/// The type of the view model. +/// +/// +public class ReactiveTitleBar< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes + .PublicParameterlessConstructor)] +TViewModel> : TitleBar, IViewFor + where TViewModel : class +{ + /// The view model bindable property. + public static readonly BindableProperty ViewModelProperty = BindableProperty.Create( + nameof(ViewModel), + typeof(TViewModel), + typeof(ReactiveTitleBar), + propertyChanged: OnViewModelChanged); + + /// Gets or sets the ViewModel to display. + public TViewModel? ViewModel + { + get => (TViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TViewModel?)value; + } + + /// + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + ViewModel = BindingContext as TViewModel; + } + + /// Updates the binding context when the view model changes. + /// The bindable object whose property changed. + /// The previous value. + /// The new value. + private static void OnViewModelChanged(BindableObject bindableObject, object oldValue, object newValue) => + bindableObject.BindingContext = newValue; +} diff --git a/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj b/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj index 2824402c5e..d0b453fc9b 100644 --- a/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj +++ b/src/ReactiveUI.Maui/ReactiveUI.Maui.csproj @@ -41,7 +41,7 @@ - + diff --git a/src/ReactiveUI.Maui/ReactiveWindow.cs b/src/ReactiveUI.Maui/ReactiveWindow.cs new file mode 100644 index 0000000000..18e4f5068b --- /dev/null +++ b/src/ReactiveUI.Maui/ReactiveWindow.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.Maui.Controls; + +#if REACTIVE_SHIM +namespace ReactiveUI.Reactive.Maui; +#else +namespace ReactiveUI.Maui; +#endif + +/// This is a that is also an . +/// The type of the view model. +/// +/// +public class ReactiveWindow< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes + .PublicParameterlessConstructor)] +TViewModel> : Window, IViewFor + where TViewModel : class +{ + /// The view model bindable property. + public static readonly BindableProperty ViewModelProperty = BindableProperty.Create( + nameof(ViewModel), + typeof(TViewModel), + typeof(ReactiveWindow), + propertyChanged: OnViewModelChanged); + + /// Gets or sets the ViewModel to display. + public TViewModel? ViewModel + { + get => (TViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TViewModel?)value; + } + + /// + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + ViewModel = BindingContext as TViewModel; + } + + /// Updates the binding context when the view model changes. + /// The bindable object whose property changed. + /// The previous value. + /// The new value. + private static void OnViewModelChanged(BindableObject bindableObject, object oldValue, object newValue) => + bindableObject.BindingContext = newValue; +} diff --git a/src/ReactiveUI.Maui/RoutedViewHost.cs b/src/ReactiveUI.Maui/RoutedViewHost.cs index 7ced0ef9da..1571dd8b34 100644 --- a/src/ReactiveUI.Maui/RoutedViewHost.cs +++ b/src/ReactiveUI.Maui/RoutedViewHost.cs @@ -154,7 +154,7 @@ protected virtual Page PageForViewModel(IRoutableViewModel vm) if (SetTitleOnNavigate) { - RxSchedulers.MainThreadScheduler.Schedule(() => pg.Title = vm.UrlPathSegment); + _ = RxSchedulers.MainThreadScheduler.Schedule(() => pg.Title = vm.UrlPathSegment); } return pg; @@ -314,19 +314,19 @@ private void OnNavigateRequested() return; } - RxSchedulers.MainThreadScheduler.Schedule(() => _ = OnNavigateAsync()); + _ = RxSchedulers.MainThreadScheduler.Schedule(() => _ = OnNavigateAsync()); } - /// Resolves the page for the current view model and pushes it, then resyncs the stacks. - /// A representing the asynchronous operation. + /// Resolves the page for the current view model. PagesForViewModel emits a single page synchronously + /// (or signals an error), so the subscription resolves it inline. + /// The resolved page, or if none could be resolved. [RequiresUnreferencedCode( "This method uses reflection to determine the view model type at runtime, which may be incompatible with trimming.")] [RequiresDynamicCode( "If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic arguments), " + "trimming can't validate that the requirements of those annotations are met.")] - private async Task OnNavigateAsync() + private Page? ResolveCurrentPage() { - // PagesForViewModel emits a single page synchronously (or signals an error), so the subscription resolves it inline. Page? page = null; PagesForViewModel(Router.GetCurrentViewModel()) .Subscribe(new DelegateObserver( @@ -334,6 +334,20 @@ private async Task OnNavigateAsync() e => this.Log().Error(e, "Failed to resolve the page for navigation"))) .Dispose(); + return page; + } + + /// Resolves the page for the current view model and pushes it, then resyncs the stacks. + /// A representing the asynchronous operation. + [RequiresUnreferencedCode( + "This method uses reflection to determine the view model type at runtime, which may be incompatible with trimming.")] + [RequiresDynamicCode( + "If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic arguments), " + + "trimming can't validate that the requirements of those annotations are met.")] + private async Task OnNavigateAsync() + { + var page = ResolveCurrentPage(); + if (page is null) { return; @@ -365,7 +379,7 @@ private void SubscribeToPopped() }); // NB: User pressed the Application back as opposed to requesting Back via Router.NavigateBack. - poppingEvent + _ = poppingEvent .Subscribe(new DelegateObserver(_ => { // Replaces .Where(_ => !_currentlyNavigating && Router is not null). @@ -395,7 +409,7 @@ private void SubscribeToPoppedToRoot() return new ActionDisposable(() => PoppedToRoot -= Handler); }); - poppingToRootEvent + _ = poppingToRootEvent .Subscribe(new DelegateObserver(_ => { // Replaces .Where(_ => !_currentlyNavigating && Router is not null). diff --git a/src/ReactiveUI.Maui/RoutedViewHost{TViewModel}.cs b/src/ReactiveUI.Maui/RoutedViewHost{TViewModel}.cs index 60b84b1cf4..53d4b78922 100644 --- a/src/ReactiveUI.Maui/RoutedViewHost{TViewModel}.cs +++ b/src/ReactiveUI.Maui/RoutedViewHost{TViewModel}.cs @@ -51,8 +51,8 @@ protected override IObservable PagesForViewModel(IRoutableViewModel? vm) var ret = ViewLocator.Current.ResolveView(); if (ret is null) { - var msg = - $"Couldn't find a View for ViewModel. You probably need to register an IViewFor<{typeof(TViewModel).Name}>"; + const string msg = + $"Couldn't find a View for ViewModel. You probably need to register an IViewFor<{nameof(TViewModel)}>"; return Signal.Fail(new InvalidOperationException(msg)); } @@ -84,8 +84,8 @@ protected override Page PageForViewModel(IRoutableViewModel vm) var ret = ViewLocator.Current.ResolveView(); if (ret is null) { - var msg = - $"Couldn't find a View for ViewModel. You probably need to register an IViewFor<{typeof(TViewModel).Name}>"; + const string msg = + $"Couldn't find a View for ViewModel. You probably need to register an IViewFor<{nameof(TViewModel)}>"; throw new InvalidOperationException(msg); } @@ -96,7 +96,7 @@ protected override Page PageForViewModel(IRoutableViewModel vm) if (SetTitleOnNavigate) { - RxSchedulers.MainThreadScheduler.Schedule(() => pg.Title = vm.UrlPathSegment); + _ = RxSchedulers.MainThreadScheduler.Schedule(() => pg.Title = vm.UrlPathSegment); } return pg; diff --git a/src/ReactiveUI.Maui/ViewModelViewHost.cs b/src/ReactiveUI.Maui/ViewModelViewHost.cs index 6fa79777d0..711a223b46 100644 --- a/src/ReactiveUI.Maui/ViewModelViewHost.cs +++ b/src/ReactiveUI.Maui/ViewModelViewHost.cs @@ -73,7 +73,7 @@ public ViewModelViewHost() ViewContractObservable = Signal.Emit(null); // Re-resolve when the contract changes; ViewModel changes are handled by OnViewModelPropertyChanged. - ViewContractObservable + _ = ViewContractObservable .Subscribe(new DelegateObserver(contract => { _viewContract = contract; diff --git a/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs b/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs index 371f0d35f9..13f19c093b 100644 --- a/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs +++ b/src/ReactiveUI.Maui/WinUI/DependencyObjectObservableForProperty.cs @@ -41,12 +41,7 @@ public int GetAffinityForObject(Type? type, string propertyName, bool beforeChan return 0; } - if (GetDependencyPropertyFetcher(type, propertyName) is null) - { - return 0; - } - - return DependencyPropertyAffinity; + return GetDependencyPropertyFetcher(type, propertyName) is null ? 0 : DependencyPropertyAffinity; } /// @@ -167,12 +162,7 @@ public int GetAffinityForObject(Type? type, string propertyName, bool beforeChan { var value = pi.GetValue(null); - if (value is null) - { - return null; - } - - return () => (DependencyProperty)value; + return value is null ? null : () => (DependencyProperty)value; } var fi = ActuallyGetField(typeInfo, propertyName + "Property"); @@ -180,12 +170,7 @@ public int GetAffinityForObject(Type? type, string propertyName, bool beforeChan { var value = fi.GetValue(null); - if (value is null) - { - return null; - } - - return () => (DependencyProperty)value; + return value is null ? null : () => (DependencyProperty)value; } return null; diff --git a/src/ReactiveUI.Shared/Activation/ViewForMixins.cs b/src/ReactiveUI.Shared/Activation/ViewForMixins.cs index 7ca28ca341..68d00f5b84 100644 --- a/src/ReactiveUI.Shared/Activation/ViewForMixins.cs +++ b/src/ReactiveUI.Shared/Activation/ViewForMixins.cs @@ -29,16 +29,7 @@ public static class ViewForMixins { /// Cache mapping view types to their resolved activation fetcher, to avoid repeated service locator lookups. private static readonly MemoizingMRUCache _activationFetcherCache = - new( - (t, _) => - AppLocator.Current - .GetServices() - .Aggregate((count: 0, viewFetcher: default(IActivationForViewFetcher?)), (acc, x) => - { - var score = x?.GetAffinityForView(t) ?? 0; - return score > acc.count ? (score, x) : acc; - }).viewFetcher, - RxCacheSize.SmallCacheLimit); + new((t, _) => ResolveActivationFetcher(t), RxCacheSize.SmallCacheLimit); /// Provides activation lifecycle extension members for . /// The view whose activation lifecycle will manage the disposables. @@ -263,6 +254,26 @@ public void /// internal static void ResetActivationFetcherCacheForTesting() => _activationFetcherCache.InvalidateAll(); + /// Selects the registered activation fetcher with the highest affinity for the given view type. + /// The view type to resolve an activation fetcher for. + /// The highest-affinity fetcher, or when none have positive affinity. + private static IActivationForViewFetcher? ResolveActivationFetcher(Type viewType) + { + var bestScore = 0; + IActivationForViewFetcher? best = null; + foreach (var fetcher in AppLocator.Current.GetServices()) + { + var score = fetcher?.GetAffinityForView(viewType) ?? 0; + if (score > bestScore) + { + bestScore = score; + best = fetcher; + } + } + + return best; + } + /// /// Manages the activation and deactivation lifecycle of a view by subscribing to an activation observable and /// invoking a resource allocation block when activated. diff --git a/src/ReactiveUI.Shared/Activation/ViewModelActivator.cs b/src/ReactiveUI.Shared/Activation/ViewModelActivator.cs index c6d26692a3..7ea0394c22 100644 --- a/src/ReactiveUI.Shared/Activation/ViewModelActivator.cs +++ b/src/ReactiveUI.Shared/Activation/ViewModelActivator.cs @@ -117,7 +117,7 @@ public void Deactivate(bool ignoreRefCount) { if (ignoreRefCount) { - Interlocked.Exchange(ref _refCount, 0); + _ = Interlocked.Exchange(ref _refCount, 0); Interlocked.Exchange(ref _activationHandle, EmptyDisposable.Instance).Dispose(); _deactivated.OnNext(RxVoid.Default); return; diff --git a/src/ReactiveUI.Shared/Bindings/Property/Internal/BindingHookEvaluator.cs b/src/ReactiveUI.Shared/Bindings/Property/Internal/BindingHookEvaluator.cs index 481eae2818..de0e3429ee 100644 --- a/src/ReactiveUI.Shared/Bindings/Property/Internal/BindingHookEvaluator.cs +++ b/src/ReactiveUI.Shared/Bindings/Property/Internal/BindingHookEvaluator.cs @@ -46,14 +46,14 @@ public bool EvaluateBindingHooks( return []; } - viewModelChain!.TryGetAllValues(viewModel, out var fetchedValues); + _ = viewModelChain!.TryGetAllValues(viewModel, out var fetchedValues); return fetchedValues; } - : () => [new ObservedChange(null!, null, viewModel)]; + : () => [new ObservedChange(null!, null, viewModel)]; var viewFetcher = () => { - viewChainGetter.TryGetAllValues(view, out var fetchedValues); + _ = viewChainGetter.TryGetAllValues(view, out var fetchedValues); return fetchedValues; }; @@ -79,8 +79,8 @@ public bool EvaluateBindingHooks( return shouldBind; } - var viewModelString = $"{typeof(TViewModel).Name}.{string.Join(".", viewModelExpression)}"; - var viewString = $"{typeof(TView).Name}.{string.Join(".", viewExpression)}"; + var viewModelString = $"{nameof(TViewModel)}.{string.Join(".", viewModelExpression)}"; + var viewString = $"{nameof(TView)}.{string.Join(".", viewExpression)}"; LogHost.Default.Warn($"Binding hook asked to disable binding {viewModelString} => {viewString}"); return shouldBind; diff --git a/src/ReactiveUI.Shared/Bindings/Property/Internal/PropertyBindingExpressionCompiler.cs b/src/ReactiveUI.Shared/Bindings/Property/Internal/PropertyBindingExpressionCompiler.cs index 83e5d9d5b4..1fe96e2d35 100644 --- a/src/ReactiveUI.Shared/Bindings/Property/Internal/PropertyBindingExpressionCompiler.cs +++ b/src/ReactiveUI.Shared/Bindings/Property/Internal/PropertyBindingExpressionCompiler.cs @@ -400,12 +400,7 @@ private static void OnValueChanged( ArgumentExceptionHelper.ThrowIfNull(target); ArgumentExceptionHelper.ThrowIfNull(hostExpressionChain); - if (!Reflection.TryGetValueForPropertyChain(out object? host, target, hostExpressionChain)) - { - return null; - } - - return host; + return !Reflection.TryGetValueForPropertyChain(out object? host, target, hostExpressionChain) ? null : host; } } diff --git a/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.Conversions.cs b/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.Conversions.cs index 0b1a7a0f83..9357b9f8a4 100644 --- a/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.Conversions.cs +++ b/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.Conversions.cs @@ -15,47 +15,24 @@ namespace ReactiveUI; [SuppressMessage("Design", "SST1432:Mark the type as static", Justification = "Partial of a non-static type; the instance members live in the primary PropertyBinderImplementation partial.")] public partial class PropertyBinderImplementation { - /// Dispatches a single conversion attempt to a converter, choosing the typed or fallback conversion path. - /// The converter to use. - /// The type being converted from. - /// The type being converted to. + /// Dispatches a single conversion attempt to an explicitly supplied typed converter. + /// The typed converter to use. /// The value to convert. /// An optional hint passed to the converter. /// The converted value when conversion succeeds. /// when the converter produced a value; otherwise . private static bool TryConvertUsingConverter( - object converter, - Type sourceType, - Type targetType, + IBindingTypeConverter converter, object? value, object? conversionHint, - out object? result) - { - switch (converter) - { - case IBindingTypeConverter typedConverter: - return typedConverter.TryConvertTyped(value, conversionHint, out result); - case IBindingFallbackConverter when value is null: - { - result = null; - return false; - } - - case IBindingFallbackConverter fallbackConverter: - return fallbackConverter.TryConvert(sourceType, value, targetType, conversionHint, out result); - default: - { - result = null; - return false; - } - } - } + out object? result) => + converter.TryConvertTyped(value, conversionHint, out result); /// /// Attempts a conversion using an explicitly supplied converter, falling back to a registry /// converter registered for the same type pair when the supplied converter does not apply. /// - /// The converter explicitly supplied by the caller. + /// The typed converter explicitly supplied by the caller. /// The type being converted from. /// The type being converted to. /// The value to convert. @@ -63,25 +40,22 @@ private static bool TryConvertUsingConverter( /// The converted value when conversion succeeds. /// when a value was produced; otherwise . private static bool TryConvertWithOverride( - object converter, + IBindingTypeConverter converter, Type sourceType, Type targetType, object? value, object? conversionHint, out object? converted) { - if (TryConvertUsingConverter(converter, sourceType, targetType, value, conversionHint, out converted)) + if (TryConvertUsingConverter(converter, value, conversionHint, out converted)) { return true; } var fallbackConverter = GetConverterForTypes(sourceType, targetType); - if (fallbackConverter is null || fallbackConverter == converter) - { - return false; - } - - return BindingTypeConverterDispatch.TryConvertAny(fallbackConverter, sourceType, value, targetType, conversionHint, out converted); + return fallbackConverter is not null + && fallbackConverter != converter + && BindingTypeConverterDispatch.TryConvertAny(fallbackConverter, sourceType, value, targetType, conversionHint, out converted); } /// Converts a view model property value into the corresponding view property value. @@ -111,7 +85,7 @@ private static bool TryConvertViewModelToView.Default.Equals(viewValue, viewModelAsView)) - { - return (false, null, false); - } - - return (true, viewModelAsView, true); - } - - if (!viewToViewModelConverter(viewValue, out var viewAsViewModel) || - EqualityComparer.Default.Equals(viewModelValue, viewAsViewModel)) - { - return (false, null, false); + return !viewModelToViewConverter(viewModelValue, out var viewModelAsView) || + EqualityComparer.Default.Equals(viewValue, viewModelAsView) ? (false, null, false) : (true, viewModelAsView, true); } - return (true, viewAsViewModel, false); + return !viewToViewModelConverter(viewValue, out var viewAsViewModel) || + EqualityComparer.Default.Equals(viewModelValue, viewAsViewModel) ? (false, null, false) : (true, viewAsViewModel, false); } /// Builds the merged observable that signals when either the view model or the view side changed. diff --git a/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.cs b/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.cs index 20545465df..acd6451fba 100644 --- a/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.cs +++ b/src/ReactiveUI.Shared/Bindings/Property/PropertyBinderImplementation.cs @@ -506,12 +506,7 @@ public IDisposable BindTo( return (true, tmp); } - if (viewModelToViewConverterOverride is null && typeof(TTargetValue).IsAssignableFrom(typeof(TValue))) - { - return (true, (object?)x); - } - - return (false, null); + return viewModelToViewConverterOverride is null && typeof(TTargetValue).IsAssignableFrom(typeof(TValue)) ? (true, (object?)x) : (false, null); }); var (disposable, _) = BindToDirect(source, target, viewExpression); @@ -615,7 +610,8 @@ protected virtual void SetViewValue(TView view, Action setter) viewExpression, getter, setter, - _converterResolver.GetSetMethodConverter) : _expressionCompiler.CreateChainedSetObservable( + _converterResolver.GetSetMethodConverter) + : _expressionCompiler.CreateChainedSetObservable( target, changeObservable, viewExpression, @@ -742,7 +738,7 @@ private IDisposable SubscribeWithBindingErrorHandling( } else { - viewModelChainSetter.TrySetValue(view.ViewModel, latestValue.view, false); + _ = viewModelChainSetter.TrySetValue(view.ViewModel, latestValue.view, false); } })); diff --git a/src/ReactiveUI.Shared/Builder/ReactiveUIBuilder.cs b/src/ReactiveUI.Shared/Builder/ReactiveUIBuilder.cs index e699bd43e6..471957cf2b 100644 --- a/src/ReactiveUI.Shared/Builder/ReactiveUIBuilder.cs +++ b/src/ReactiveUI.Shared/Builder/ReactiveUIBuilder.cs @@ -146,7 +146,7 @@ public IReactiveUIBuilder WithTaskPoolScheduler(ISequencer scheduler, bool setRx /// The builder instance for chaining. public IReactiveUIBuilder WithRegistrationOnBuild(Action configureAction) { - WithCustomRegistration(configureAction); + _ = WithCustomRegistration(configureAction); return this; } @@ -187,7 +187,7 @@ public IReactiveUIBuilder WithPlatformServices() return this; } - WithPlatformModule(); + _ = WithPlatformModule(); _platformRegistered = true; return this; @@ -201,7 +201,7 @@ public override IAppBuilder WithCoreServices() { RegisterStandardConverters(); - WithPlatformModule(); + _ = WithPlatformModule(); _coreRegistered = true; } @@ -248,7 +248,7 @@ public IReactiveUIBuilder WithPlatformModule() public IReactiveUIBuilder UsingSplatModule(T registrationModule) where T : IModule { - UsingModule(registrationModule); + _ = UsingModule(registrationModule); return this; } diff --git a/src/ReactiveUI.Shared/Expression/Reflection.cs b/src/ReactiveUI.Shared/Expression/Reflection.cs index 41f1fced17..a9415fb0b6 100644 --- a/src/ReactiveUI.Shared/Expression/Reflection.cs +++ b/src/ReactiveUI.Shared/Expression/Reflection.cs @@ -78,7 +78,7 @@ public static string ExpressionToPropertyNames(Expression? expression) if (!firstSegment) { - sb.Append('.'); + _ = sb.Append('.'); } switch (exp.NodeType) @@ -87,26 +87,26 @@ public static string ExpressionToPropertyNames(Expression? expression) exp is IndexExpression indexExpression && indexExpression.Indexer is not null: { - sb.Append(indexExpression.Indexer.Name).Append('['); + _ = sb.Append(indexExpression.Indexer.Name).Append('['); var args = indexExpression.Arguments; for (var i = 0; i < args.Count; i++) { if (i != 0) { - sb.Append(','); + _ = sb.Append(','); } - sb.Append(((ConstantExpression)args[i]).Value); + _ = sb.Append(((ConstantExpression)args[i]).Value); } - sb.Append(']'); + _ = sb.Append(']'); break; } case ExpressionType.MemberAccess when exp is MemberExpression memberExpression: { - sb.Append(memberExpression.Member.Name); + _ = sb.Append(memberExpression.Member.Name); break; } } @@ -146,12 +146,7 @@ exp is IndexExpression indexExpression && }; } - if (member is not PropertyInfo property) - { - return null; - } - - return property.GetValue; + return member is not PropertyInfo property ? null : property.GetValue; } /// @@ -197,12 +192,7 @@ exp is IndexExpression indexExpression && return (obj, val, _) => field.SetValue(obj, val); } - if (member is not PropertyInfo property) - { - return null; - } - - return property.SetValue; + return member is not PropertyInfo property ? null : property.SetValue; } /// diff --git a/src/ReactiveUI.Shared/Interactions/Interaction.cs b/src/ReactiveUI.Shared/Interactions/Interaction.cs index 6b4fa84d73..92b7d1873e 100644 --- a/src/ReactiveUI.Shared/Interactions/Interaction.cs +++ b/src/ReactiveUI.Shared/Interactions/Interaction.cs @@ -200,7 +200,7 @@ private void RemoveHandler(Func, IObservabl { lock (_sync) { - _handlers.Remove(handler); + _ = _handlers.Remove(handler); _handlersSnapshot = null; } } diff --git a/src/ReactiveUI.Shared/Internal/InteractionHandleObservable.cs b/src/ReactiveUI.Shared/Internal/InteractionHandleObservable.cs index 7e91a767cf..46a1582087 100644 --- a/src/ReactiveUI.Shared/Internal/InteractionHandleObservable.cs +++ b/src/ReactiveUI.Shared/Internal/InteractionHandleObservable.cs @@ -41,7 +41,8 @@ public IDisposable Subscribe(IObserver observer) } /// Drives the sequential, scheduler-bound handler run for a single subscription. - private sealed class Sink : IDisposable + /// Internal so the disposed-during-step guard can be exercised directly in tests. + internal sealed class Sink : IDisposable { /// The observer receiving the interaction output. private readonly IObserver _observer; @@ -104,38 +105,10 @@ public void Dispose() _current.Dispose(); } - /// Schedules the next handler step on the handler scheduler. - /// The handler index to run (descending toward the first-registered handler). - private void RunFrom(int index) - { - // Saved/restored across the (possibly re-entrant) scheduling call so a nested RunFrom — triggered when a - // handler completes synchronously and advances the run inline — does not clobber the outer frame's view. - var previousRanInline = _stepRanInline; - _stepRanInline = false; - - var scheduled = _scheduler.ScheduleOrInline( - (Self: this, Index: index), - static (_, state) => - { - state.Self._stepRanInline = true; - state.Self.Step(state.Index); - return EmptyDisposable.Instance; - }); - - // Only adopt the scheduling disposable when the step is genuinely pending (queued). When it ran inline the - // step already took ownership of _current (a live handler subscription, the next scheduled step, or the - // finished run), and overwriting that here would dispose the live work and stall the run. - if (!_stepRanInline) - { - _current.Disposable = scheduled; - } - - _stepRanInline = previousRanInline; - } - /// Runs the handler at , or finishes when handled or exhausted. /// The handler index to run. - private void Step(int index) + /// Internal so the disposed-during-step guard can be exercised directly in tests. + internal void Step(int index) { if (_disposed) { @@ -173,6 +146,45 @@ private void Step(int index) _current.Disposable = subscription; } + /// Schedules the next handler step on the handler scheduler. + /// The handler index to run (descending toward the first-registered handler). + private void RunFrom(int index) + { + // Saved/restored across the (possibly re-entrant) scheduling call so a nested RunFrom — triggered when a + // handler completes synchronously and advances the run inline — does not clobber the outer frame's view. + var previousRanInline = _stepRanInline; + _stepRanInline = false; + + var scheduled = _scheduler.ScheduleOrInline( + (Self: this, Index: index), + static (_, state) => + { + state.Self._stepRanInline = true; + state.Self.Step(state.Index); + return EmptyDisposable.Instance; + }); + + // Only adopt the scheduling disposable when the step is genuinely pending (queued). When it ran inline the + // step already took ownership of _current (a live handler subscription, the next scheduled step, or the + // finished run), and overwriting that here would dispose the live work and stall the run. + AdoptIfPending(scheduled); + + _stepRanInline = previousRanInline; + } + + /// Adopts the scheduling disposable into only when the step is still pending — + /// that is, the scheduled callback did not run inline and take ownership of itself. + /// The disposable returned by the scheduling call. + private void AdoptIfPending(IDisposable scheduled) + { + if (_stepRanInline) + { + return; + } + + _current.Disposable = scheduled; + } + /// Emits the output once handled, or the unhandled-interaction error otherwise. private void Finish() { diff --git a/src/ReactiveUI.Shared/Internal/TaskUnitObservable.cs b/src/ReactiveUI.Shared/Internal/TaskUnitObservable.cs index a818a30908..3d2ef44300 100644 --- a/src/ReactiveUI.Shared/Internal/TaskUnitObservable.cs +++ b/src/ReactiveUI.Shared/Internal/TaskUnitObservable.cs @@ -22,7 +22,7 @@ internal sealed class TaskUnitObservable(Task task) : IObservable public IDisposable Subscribe(IObserver observer) { ArgumentExceptionHelper.ThrowIfNull(observer); - task.ContinueWith( + _ = task.ContinueWith( static (completed, state) => { var observer = (IObserver)state!; diff --git a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity11.cs b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity11.cs index 22d524ac2d..c506562054 100644 --- a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity11.cs +++ b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity11.cs @@ -149,6 +149,9 @@ private sealed class Sink(IObserver downstream, FuncThe number of sources that have not yet completed. private int _active = 11; + /// The number of sources that have produced a value. + private int _ready; + /// Subscribes to every source, wiring each notification back into the sink. /// Source observable 1. /// Source observable 2. @@ -195,13 +198,16 @@ public void On1(T1 change) lock (_gate) { _value1 = change; - _has1 = true; - if (!(_has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has1) { - return; + _has1 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -212,13 +218,16 @@ public void On2(T2 change) lock (_gate) { _value2 = change; - _has2 = true; - if (!(_has1 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has2) { - return; + _has2 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -229,13 +238,16 @@ public void On3(T3 change) lock (_gate) { _value3 = change; - _has3 = true; - if (!(_has1 && _has2 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has3) { - return; + _has3 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -246,13 +258,16 @@ public void On4(T4 change) lock (_gate) { _value4 = change; - _has4 = true; - if (!(_has1 && _has2 && _has3 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has4) { - return; + _has4 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -263,13 +278,16 @@ public void On5(T5 change) lock (_gate) { _value5 = change; - _has5 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has5) { - return; + _has5 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -280,13 +298,16 @@ public void On6(T6 change) lock (_gate) { _value6 = change; - _has6 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has6) { - return; + _has6 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -297,13 +318,16 @@ public void On7(T7 change) lock (_gate) { _value7 = change; - _has7 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has8 && _has9 && _has10 && _has11)) + if (!_has7) { - return; + _has7 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -314,13 +338,16 @@ public void On8(T8 change) lock (_gate) { _value8 = change; - _has8 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has9 && _has10 && _has11)) + if (!_has8) { - return; + _has8 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -331,13 +358,16 @@ public void On9(T9 change) lock (_gate) { _value9 = change; - _has9 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has10 && _has11)) + if (!_has9) { - return; + _has9 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -348,13 +378,16 @@ public void On10(T10 change) lock (_gate) { _value10 = change; - _has10 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has11)) + if (!_has10) { - return; + _has10 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -365,13 +398,16 @@ public void On11(T11 change) lock (_gate) { _value11 = change; - _has11 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10)) + if (!_has11) { - return; + _has11 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } diff --git a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity12.cs b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity12.cs index a08d30c35c..2e19dfc0aa 100644 --- a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity12.cs +++ b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyChangeSink.Arity12.cs @@ -158,6 +158,9 @@ private sealed class Sink(IObserver downstream, FuncThe number of sources that have not yet completed. private int _active = 12; + /// The number of sources that have produced a value. + private int _ready; + /// Subscribes to every source, wiring each notification back into the sink. /// Source observable 1. /// Source observable 2. @@ -207,13 +210,16 @@ public void On1(T1 change) lock (_gate) { _value1 = change; - _has1 = true; - if (!(_has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has1) { - return; + _has1 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -224,13 +230,16 @@ public void On2(T2 change) lock (_gate) { _value2 = change; - _has2 = true; - if (!(_has1 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has2) { - return; + _has2 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -241,13 +250,16 @@ public void On3(T3 change) lock (_gate) { _value3 = change; - _has3 = true; - if (!(_has1 && _has2 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has3) { - return; + _has3 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -258,13 +270,16 @@ public void On4(T4 change) lock (_gate) { _value4 = change; - _has4 = true; - if (!(_has1 && _has2 && _has3 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has4) { - return; + _has4 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -275,13 +290,16 @@ public void On5(T5 change) lock (_gate) { _value5 = change; - _has5 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has5) { - return; + _has5 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -292,13 +310,16 @@ public void On6(T6 change) lock (_gate) { _value6 = change; - _has6 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has6) { - return; + _has6 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -309,13 +330,16 @@ public void On7(T7 change) lock (_gate) { _value7 = change; - _has7 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has7) { - return; + _has7 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -326,13 +350,16 @@ public void On8(T8 change) lock (_gate) { _value8 = change; - _has8 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has9 && _has10 && _has11 && _has12)) + if (!_has8) { - return; + _has8 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -343,13 +370,16 @@ public void On9(T9 change) lock (_gate) { _value9 = change; - _has9 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has10 && _has11 && _has12)) + if (!_has9) { - return; + _has9 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -360,13 +390,16 @@ public void On10(T10 change) lock (_gate) { _value10 = change; - _has10 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has11 && _has12)) + if (!_has10) { - return; + _has10 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -377,13 +410,16 @@ public void On11(T11 change) lock (_gate) { _value11 = change; - _has11 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has12)) + if (!_has11) { - return; + _has11 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -394,13 +430,16 @@ public void On12(T12 change) lock (_gate) { _value12 = change; - _has12 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has12) { - return; + _has12 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } diff --git a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity11.cs b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity11.cs index 6fb2373e57..6e41501d7b 100644 --- a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity11.cs +++ b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity11.cs @@ -151,6 +151,9 @@ private sealed class Sink(IObserver downstream, FuncThe number of sources that have not yet completed. private int _active = 11; + /// The number of sources that have produced a value. + private int _ready; + /// Subscribes to every source, wiring each notification back into the sink. /// Source observable 1. /// Source observable 2. @@ -197,13 +200,16 @@ public void On1(IObservedChange change) lock (_gate) { _value1 = change.Value; - _has1 = true; - if (!(_has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has1) { - return; + _has1 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -214,13 +220,16 @@ public void On2(IObservedChange change) lock (_gate) { _value2 = change.Value; - _has2 = true; - if (!(_has1 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has2) { - return; + _has2 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -231,13 +240,16 @@ public void On3(IObservedChange change) lock (_gate) { _value3 = change.Value; - _has3 = true; - if (!(_has1 && _has2 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has3) { - return; + _has3 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -248,13 +260,16 @@ public void On4(IObservedChange change) lock (_gate) { _value4 = change.Value; - _has4 = true; - if (!(_has1 && _has2 && _has3 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has4) { - return; + _has4 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -265,13 +280,16 @@ public void On5(IObservedChange change) lock (_gate) { _value5 = change.Value; - _has5 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has5) { - return; + _has5 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -282,13 +300,16 @@ public void On6(IObservedChange change) lock (_gate) { _value6 = change.Value; - _has6 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has6) { - return; + _has6 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -299,13 +320,16 @@ public void On7(IObservedChange change) lock (_gate) { _value7 = change.Value; - _has7 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has8 && _has9 && _has10 && _has11)) + if (!_has7) { - return; + _has7 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -316,13 +340,16 @@ public void On8(IObservedChange change) lock (_gate) { _value8 = change.Value; - _has8 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has9 && _has10 && _has11)) + if (!_has8) { - return; + _has8 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -333,13 +360,16 @@ public void On9(IObservedChange change) lock (_gate) { _value9 = change.Value; - _has9 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has10 && _has11)) + if (!_has9) { - return; + _has9 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -350,13 +380,16 @@ public void On10(IObservedChange change) lock (_gate) { _value10 = change.Value; - _has10 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has11)) + if (!_has10) { - return; + _has10 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -367,13 +400,16 @@ public void On11(IObservedChange change) lock (_gate) { _value11 = change.Value; - _has11 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10)) + if (!_has11) { - return; + _has11 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } diff --git a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity12.cs b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity12.cs index 0f8e45a462..0947085d01 100644 --- a/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity12.cs +++ b/src/ReactiveUI.Shared/Internal/WhenAny/WhenAnyValueSink.Arity12.cs @@ -160,6 +160,9 @@ private sealed class Sink(IObserver downstream, FuncThe number of sources that have not yet completed. private int _active = 12; + /// The number of sources that have produced a value. + private int _ready; + /// Subscribes to every source, wiring each notification back into the sink. /// Source observable 1. /// Source observable 2. @@ -209,13 +212,16 @@ public void On1(IObservedChange change) lock (_gate) { _value1 = change.Value; - _has1 = true; - if (!(_has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has1) { - return; + _has1 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -226,13 +232,16 @@ public void On2(IObservedChange change) lock (_gate) { _value2 = change.Value; - _has2 = true; - if (!(_has1 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has2) { - return; + _has2 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -243,13 +252,16 @@ public void On3(IObservedChange change) lock (_gate) { _value3 = change.Value; - _has3 = true; - if (!(_has1 && _has2 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has3) { - return; + _has3 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -260,13 +272,16 @@ public void On4(IObservedChange change) lock (_gate) { _value4 = change.Value; - _has4 = true; - if (!(_has1 && _has2 && _has3 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has4) { - return; + _has4 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -277,13 +292,16 @@ public void On5(IObservedChange change) lock (_gate) { _value5 = change.Value; - _has5 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has5) { - return; + _has5 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -294,13 +312,16 @@ public void On6(IObservedChange change) lock (_gate) { _value6 = change.Value; - _has6 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has7 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has6) { - return; + _has6 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -311,13 +332,16 @@ public void On7(IObservedChange change) lock (_gate) { _value7 = change.Value; - _has7 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has8 && _has9 && _has10 && _has11 && _has12)) + if (!_has7) { - return; + _has7 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -328,13 +352,16 @@ public void On8(IObservedChange change) lock (_gate) { _value8 = change.Value; - _has8 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has9 && _has10 && _has11 && _has12)) + if (!_has8) { - return; + _has8 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -345,13 +372,16 @@ public void On9(IObservedChange change) lock (_gate) { _value9 = change.Value; - _has9 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has10 && _has11 && _has12)) + if (!_has9) { - return; + _has9 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -362,13 +392,16 @@ public void On10(IObservedChange change) lock (_gate) { _value10 = change.Value; - _has10 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has11 && _has12)) + if (!_has10) { - return; + _has10 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -379,13 +412,16 @@ public void On11(IObservedChange change) lock (_gate) { _value11 = change.Value; - _has11 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has12)) + if (!_has11) { - return; + _has11 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } @@ -396,13 +432,16 @@ public void On12(IObservedChange change) lock (_gate) { _value12 = change.Value; - _has12 = true; - if (!(_has1 && _has2 && _has3 && _has4 && _has5 && _has6 && _has7 && _has8 && _has9 && _has10 && _has11)) + if (!_has12) { - return; + _has12 = true; + _ready++; } - Emit(); + if (_ready == _subscriptions.Length) + { + Emit(); + } } } diff --git a/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.Metadata.cs b/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.Metadata.cs index 87f288de4c..1f0c9ec3ef 100644 --- a/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.Metadata.cs +++ b/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.Metadata.cs @@ -95,7 +95,7 @@ public IDisposable AutoPersist( var persistablePropertyNames = metadata.PersistablePropertyNames; var ret = new OnceDisposable(); - RxSchedulers.MainThreadScheduler.Schedule(() => + _ = RxSchedulers.MainThreadScheduler.Schedule(() => { if (ret.IsDisposed) { diff --git a/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.cs b/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.cs index 098cc1d570..45ad715511 100644 --- a/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.cs +++ b/src/ReactiveUI.Shared/Mixins/AutoPersistHelperMixins.cs @@ -472,7 +472,7 @@ public IDisposable AutoPersist( var persistablePropertyNames = metadata.PersistablePropertyNames; var ret = new OnceDisposable(); - RxSchedulers.MainThreadScheduler.Schedule(() => + _ = RxSchedulers.MainThreadScheduler.Schedule(() => { if (ret.IsDisposed) { @@ -555,16 +555,7 @@ public IDisposable AutoPersistCollection( disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, metadata, interval); }, - x => - { - if (!disposerList.TryGetValue(x, out var d)) - { - return; - } - - d.Dispose(); - disposerList.Remove(x); - }); + x => DisposeAndRemove(disposerList, x)); return new ActionDisposable(() => { @@ -631,16 +622,7 @@ public IDisposable AutoPersistCollection( var metadata = metadataProvider(x); disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, metadata, interval); }, - x => - { - if (!disposerList.TryGetValue(x, out var d)) - { - return; - } - - d.Dispose(); - disposerList.Remove(x); - }); + x => DisposeAndRemove(disposerList, x)); return new ActionDisposable(() => { @@ -710,16 +692,7 @@ public IDisposable AutoPersistCollection( disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, interval); }, - x => - { - if (!disposerList.TryGetValue(x, out var d)) - { - return; - } - - d.Dispose(); - disposerList.Remove(x); - }); + x => DisposeAndRemove(disposerList, x)); return new ActionDisposable(() => { @@ -806,6 +779,22 @@ public static AutoPersistMetadata CreateMetadata< where T : IReactiveObject => PersistMetadataHolder.Metadata.Public; + /// Disposes and removes the tracked auto-persist subscription for an item that left the collection. + /// The item type. + /// The map of items to their auto-persist subscriptions. + /// The item that was removed from the collection. + private static void DisposeAndRemove(Dictionary disposerList, TItem item) + where TItem : notnull + { + if (!disposerList.TryGetValue(item, out var disposable)) + { + return; + } + + disposable.Dispose(); + _ = disposerList.Remove(item); + } + /// Applies a single change-set entry by invoking the add or remove callback for the affected items. /// The item type. /// The change to apply. @@ -1107,7 +1096,7 @@ internal static PersistMetadata Create( } set ??= new(StringComparer.Ordinal); - set.Add(p.Name); + _ = set.Add(p.Name); } set ??= new(StringComparer.Ordinal); diff --git a/src/ReactiveUI.Shared/Mixins/BuilderMixins.cs b/src/ReactiveUI.Shared/Mixins/BuilderMixins.cs index e3b92673d3..ee6eb8e19d 100644 --- a/src/ReactiveUI.Shared/Mixins/BuilderMixins.cs +++ b/src/ReactiveUI.Shared/Mixins/BuilderMixins.cs @@ -44,7 +44,7 @@ public IReactiveUIBuilder BuildApp() "The provided IAppBuilder is not an IReactiveUIBuilder. Ensure you are using the ReactiveUI builder pattern."); } - reactiveUiBuilder.Build(); + _ = reactiveUiBuilder.Build(); return reactiveUiBuilder; } } @@ -167,7 +167,7 @@ public IReactiveUIBuilder WithTaskPoolScheduler( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithTaskPoolScheduler(scheduler, setRxApp); + _ = builder.WithTaskPoolScheduler(scheduler, setRxApp); return builder; } @@ -194,7 +194,7 @@ public IReactiveUIBuilder WithMainThreadScheduler( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithMainThreadScheduler(scheduler, setRxApp); + _ = builder.WithMainThreadScheduler(scheduler, setRxApp); return builder; } @@ -209,7 +209,7 @@ public IReactiveUIBuilder WithRegistrationOnBuild( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithRegistrationOnBuild(configureAction); + _ = builder.WithRegistrationOnBuild(configureAction); return builder; } @@ -224,7 +224,7 @@ public IReactiveUIBuilder WithRegistration( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithRegistration(configureAction); + _ = builder.WithRegistration(configureAction); return builder; } @@ -240,7 +240,7 @@ public IReactiveUIBuilder WithViewsFromAssembly(Assembly assembly) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithViewsFromAssembly(assembly); + _ = builder.WithViewsFromAssembly(assembly); return builder; } @@ -259,7 +259,7 @@ public IReactiveUIBuilder WithPlatformModule() { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithPlatformModule(); + _ = builder.WithPlatformModule(); return builder; } @@ -275,7 +275,7 @@ public IReactiveUIBuilder UsingSplatModule(T registrationModule) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.UsingSplatModule(registrationModule); + _ = builder.UsingSplatModule(registrationModule); return builder; } @@ -289,7 +289,7 @@ public IReactiveUIBuilder WithConverter( BindingTypeConverter converter) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithConverter(converter); + _ = builder.WithConverter(converter); return builder; } @@ -301,7 +301,7 @@ public IReactiveUIBuilder WithConverter( IBindingTypeConverter converter) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithConverter(converter); + _ = builder.WithConverter(converter); return builder; } @@ -315,7 +315,7 @@ public IReactiveUIBuilder WithConverter( Func> factory) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithConverter(factory); + _ = builder.WithConverter(factory); return builder; } @@ -327,7 +327,7 @@ public IReactiveUIBuilder WithConverter( Func factory) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithConverter(factory); + _ = builder.WithConverter(factory); return builder; } @@ -343,7 +343,7 @@ public IReactiveUIBuilder WithConverters( foreach (var converter in converters) { - builder.WithConverter(converter); + _ = builder.WithConverter(converter); } return builder; @@ -361,7 +361,7 @@ public IReactiveUIBuilder WithFallbackConverter( IBindingFallbackConverter converter) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithFallbackConverter(converter); + _ = builder.WithFallbackConverter(converter); return builder; } @@ -373,7 +373,7 @@ public IReactiveUIBuilder WithFallbackConverter( Func factory) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithFallbackConverter(factory); + _ = builder.WithFallbackConverter(factory); return builder; } @@ -389,7 +389,7 @@ public IReactiveUIBuilder WithSetMethodConverter( ISetMethodBindingConverter converter) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithSetMethodConverter(converter); + _ = builder.WithSetMethodConverter(converter); return builder; } @@ -401,7 +401,7 @@ public IReactiveUIBuilder WithSetMethodConverter( Func factory) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithSetMethodConverter(factory); + _ = builder.WithSetMethodConverter(factory); return builder; } @@ -427,7 +427,7 @@ public IReactiveUIBuilder WithConvertersFrom( IReadonlyDependencyResolver resolver) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithConvertersFrom(resolver); + _ = builder.WithConvertersFrom(resolver); return builder; } @@ -458,7 +458,7 @@ public IReactiveUIBuilder ForCustomPlatform( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder + _ = builder .WithMainThreadScheduler(mainThreadScheduler) .WithRegistration(platformServices); return builder; @@ -475,7 +475,7 @@ public IReactiveUIBuilder ForPlatforms( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.ForPlatforms(platformConfigurations); + _ = builder.ForPlatforms(platformConfigurations); return builder; } @@ -488,7 +488,7 @@ public IReactiveUIBuilder WithMessageBus() { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithMessageBus(); + _ = builder.WithMessageBus(); return builder; } @@ -503,7 +503,7 @@ public IReactiveUIBuilder WithMessageBus( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithMessageBus(configure); + _ = builder.WithMessageBus(configure); return builder; } @@ -517,7 +517,7 @@ public IReactiveUIBuilder WithMessageBus(IMessageBus messageBus) { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.WithMessageBus(messageBus); + _ = builder.WithMessageBus(messageBus); return builder; } @@ -532,7 +532,7 @@ public IReactiveUIBuilder ConfigureViewLocator( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.ConfigureViewLocator(configure); + _ = builder.ConfigureViewLocator(configure); return builder; } @@ -547,7 +547,7 @@ public IReactiveUIBuilder ConfigureSuspensionDriver( { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.ConfigureSuspensionDriver(configure); + _ = builder.ConfigureSuspensionDriver(configure); return builder; } @@ -566,7 +566,7 @@ public IReactiveUIBuilder RegisterViewModel() { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.RegisterViewModel(); + _ = builder.RegisterViewModel(); return builder; } @@ -583,7 +583,7 @@ public IReactiveUIBuilder RegisterConstantViewModel() { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.RegisterConstantViewModel(); + _ = builder.RegisterConstantViewModel(); return builder; } @@ -608,7 +608,7 @@ public IReactiveUIBuilder RegisterSingletonViewModel() { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.RegisterSingletonViewModel(); + _ = builder.RegisterSingletonViewModel(); return builder; } @@ -629,7 +629,7 @@ public IReactiveUIBuilder RegisterView() { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.RegisterView(); + _ = builder.RegisterView(); return builder; } @@ -650,7 +650,7 @@ public IReactiveUIBuilder RegisterSingletonView() { ArgumentExceptionHelper.ThrowIfNull(builder); - builder.RegisterSingletonView(); + _ = builder.RegisterSingletonView(); return builder; } } diff --git a/src/ReactiveUI.Shared/Mixins/ObservedChangedMixins.cs b/src/ReactiveUI.Shared/Mixins/ObservedChangedMixins.cs index 9eb0f9d40a..daa7ec9b05 100644 --- a/src/ReactiveUI.Shared/Mixins/ObservedChangedMixins.cs +++ b/src/ReactiveUI.Shared/Mixins/ObservedChangedMixins.cs @@ -135,7 +135,7 @@ internal void SetValueToProperty( return; } - Reflection.TrySetValueToPropertyChain( + _ = Reflection.TrySetValueToPropertyChain( target, Reflection.Rewrite(property.Body).GetExpressionChain(), item.GetValue()); diff --git a/src/ReactiveUI.Shared/Mixins/ReactiveNotifyPropertyChangedMixins.cs b/src/ReactiveUI.Shared/Mixins/ReactiveNotifyPropertyChangedMixins.cs index a6ebbbb619..e339064a08 100644 --- a/src/ReactiveUI.Shared/Mixins/ReactiveNotifyPropertyChangedMixins.cs +++ b/src/ReactiveUI.Shared/Mixins/ReactiveNotifyPropertyChangedMixins.cs @@ -34,20 +34,31 @@ public static class ReactiveNotifyPropertyChangedMixins private static readonly MemoizingMRUCache<(Type? senderType, string propertyName, bool beforeChange), ICreatesObservableForProperty?> _notifyFactoryCache = - new( - (t, _) => AppLocator.Current.GetServices() - .Aggregate( - (score: 0, binding: (ICreatesObservableForProperty?)null), - (acc, x) => - { - var score = x.GetAffinityForObject(t.senderType, t.propertyName, t.beforeChange); - return score > acc.score ? (score, x) : acc; - }).binding, - RxCacheSize.BigCacheLimit); + new((t, _) => ResolveNotifyFactory(t), RxCacheSize.BigCacheLimit); /// Initializes static members of the class. static ReactiveNotifyPropertyChangedMixins() => RxAppBuilder.EnsureInitialized(); + /// Selects the registered property-notification factory with the highest affinity for the given key. + /// The sender type, property name, and change timing to resolve a factory for. + /// The highest-affinity factory, or when none have positive affinity. + private static ICreatesObservableForProperty? ResolveNotifyFactory((Type? senderType, string propertyName, bool beforeChange) key) + { + var bestScore = 0; + ICreatesObservableForProperty? best = null; + foreach (var factory in AppLocator.Current.GetServices()) + { + var score = factory.GetAffinityForObject(key.senderType, key.propertyName, key.beforeChange); + if (score > bestScore) + { + bestScore = score; + best = factory; + } + } + + return best; + } + /// Provides ObservableForProperty extension members for observing property changes on a sender object. /// The sender type. /// The source object to observe properties of. diff --git a/src/ReactiveUI.Shared/ObservableForProperty/ObservableAsPropertyHelper.cs b/src/ReactiveUI.Shared/ObservableForProperty/ObservableAsPropertyHelper.cs index 86b5fcab7f..eea2307a97 100644 --- a/src/ReactiveUI.Shared/ObservableForProperty/ObservableAsPropertyHelper.cs +++ b/src/ReactiveUI.Shared/ObservableForProperty/ObservableAsPropertyHelper.cs @@ -316,19 +316,7 @@ public T Value { if (Interlocked.CompareExchange(ref _activated, 1, 0) == 0 && !_disposed) { - _lastValue = _getInitialValue(); - var subscription = _sourceObservable.Subscribe(new SourceObserver(this)); - lock (_gate) - { - if (_disposed) - { - subscription.Dispose(); - } - else - { - _sourceSubscription = subscription; - } - } + ActivateOnFirstAccess(); } return _lastValue!; @@ -386,40 +374,29 @@ public void Dispose() _sourceSubscription?.Dispose(); } - /// The no-op onChanging callback used when none is supplied. - /// The unused value. - private static void NoOp(T? value) + /// Seeds the initial value and subscribes to the source the first time is read in deferred mode. + /// Internal so the disposed-during-activation guard can be exercised directly in tests. + internal void ActivateOnFirstAccess() { - // Intentionally empty: the default onChanging callback does nothing when the caller supplies none. - } - - /// Returns the default value of . - /// The default value. - private static T? GetDefault() => default; - - /// Schedules delivery of a value's change notifications on the configured scheduler. - /// The value to deliver. - private void ScheduleDeliver(T? value) => - _scheduler.ScheduleOrInline( - (Helper: this, Value: value), - static (_, state) => + _lastValue = _getInitialValue(); + var subscription = _sourceObservable.Subscribe(new SourceObserver(this)); + lock (_gate) + { + if (_disposed) { - state.Helper.Deliver(state.Value); - return EmptyDisposable.Instance; - }); - - /// Runs the onChanging / store / onChanged sequence for a value. - /// The value being delivered. - private void Deliver(T? value) - { - _onChanging(value); - _lastValue = value; - _onChanged(value); + subscription.Dispose(); + } + else + { + _sourceSubscription = subscription; + } + } } /// Applies the distinct / skip-initial gate to a source value and schedules delivery when it passes. /// The value produced by the source. - private void OnSourceNext(T? value) + /// Internal so the disposed-source-emission guard can be exercised directly in tests. + internal void OnSourceNext(T? value) { lock (_gate) { @@ -445,6 +422,37 @@ private void OnSourceNext(T? value) ScheduleDeliver(value); } + /// The no-op onChanging callback used when none is supplied. + /// The unused value. + private static void NoOp(T? value) + { + // Intentionally empty: the default onChanging callback does nothing when the caller supplies none. + } + + /// Returns the default value of . + /// The default value. + private static T? GetDefault() => default; + + /// Schedules delivery of a value's change notifications on the configured scheduler. + /// The value to deliver. + private void ScheduleDeliver(T? value) => + _scheduler.ScheduleOrInline( + (Helper: this, Value: value), + static (_, state) => + { + state.Helper.Deliver(state.Value); + return EmptyDisposable.Instance; + }); + + /// Runs the onChanging / store / onChanged sequence for a value. + /// The value being delivered. + private void Deliver(T? value) + { + _onChanging(value); + _lastValue = value; + _onChanged(value); + } + /// Schedules delivery of a source error to the exceptions stream. /// The error produced by the source. private void OnSourceError(Exception error) => diff --git a/src/ReactiveUI.Shared/ObservableFuncMixins.cs b/src/ReactiveUI.Shared/ObservableFuncMixins.cs index 5d8430d9dc..61b92d1929 100644 --- a/src/ReactiveUI.Shared/ObservableFuncMixins.cs +++ b/src/ReactiveUI.Shared/ObservableFuncMixins.cs @@ -140,7 +140,7 @@ public void OnError(Exception error) /// public void Dispose() { - Interlocked.Exchange(ref _disposed, 1); + _ = Interlocked.Exchange(ref _disposed, 1); _subscription.Dispose(); } } diff --git a/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandBase.cs b/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandBase.cs index 9b60da3f3c..a7dc100995 100644 --- a/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandBase.cs +++ b/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandBase.cs @@ -156,6 +156,6 @@ protected virtual void ICommandExecute(object? parameter) // Fire-and-forget: drive the execution to completion and swallow any error here // (errors are surfaced through ThrownExceptions). Replaces .Catch(Empty).Subscribe(). - result.Subscribe(new DelegateObserver(static _ => { }, static _ => { })); + _ = result.Subscribe(new DelegateObserver(static _ => { }, static _ => { })); } } diff --git a/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandMixins.cs b/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandMixins.cs index e159bdd3da..fc02e8d828 100644 --- a/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandMixins.cs +++ b/src/ReactiveUI.Shared/ReactiveCommand/ReactiveCommandMixins.cs @@ -157,7 +157,7 @@ private void OnItem(T value) return; } - command.Execute(value).Subscribe(new DelegateObserver(static _ => { }, static _ => { })); + _ = command.Execute(value).Subscribe(new DelegateObserver(static _ => { }, static _ => { })); } } @@ -321,7 +321,7 @@ private void OnItem(T value) return; } - command.Execute(value).Subscribe(new DelegateObserver(static _ => { }, static _ => { })); + _ = command.Execute(value).Subscribe(new DelegateObserver(static _ => { }, static _ => { })); } } } diff --git a/src/ReactiveUI.Shared/ReactiveObject/ExtensionState.cs b/src/ReactiveUI.Shared/ReactiveObject/ExtensionState.cs index 6c343f8bb8..791d1b4509 100644 --- a/src/ReactiveUI.Shared/ReactiveObject/ExtensionState.cs +++ b/src/ReactiveUI.Shared/ReactiveObject/ExtensionState.cs @@ -104,7 +104,7 @@ public ExtensionState(TSender sender) /// notifications. public IDisposable Suppress() { - Interlocked.Increment(ref _changeNotificationsSuppressed); + _ = Interlocked.Increment(ref _changeNotificationsSuppressed); return new ActionDisposable(() => Interlocked.Decrement(ref _changeNotificationsSuppressed)); } @@ -322,7 +322,7 @@ private Lazy> CreateLazyDelayableEventSu { var changeSubject = new DelayableNotificationSignal(AreChangeNotificationsDelayed, DistinctEvents); - changeSubject.Subscribe(new DelegateObserver(raiseEvent)); + _ = changeSubject.Subscribe(new DelegateObserver(raiseEvent)); return changeSubject; }); } diff --git a/src/ReactiveUI.Shared/ReactiveProperty/ReactiveProperty.cs b/src/ReactiveUI.Shared/ReactiveProperty/ReactiveProperty.cs index fd84b698be..3f4a5e7b2d 100644 --- a/src/ReactiveUI.Shared/ReactiveProperty/ReactiveProperty.cs +++ b/src/ReactiveUI.Shared/ReactiveProperty/ReactiveProperty.cs @@ -257,9 +257,13 @@ public ReactiveProperty AddValidationError( bool ignoreInitialError) { _validatorStore.Value.Add(validator); - var validators = _validatorStore.Value - .Select(x => x(ignoreInitialError ? _checkValidation : new PrependObservable(_checkValidation, _value))) - .ToArray(); + var validatorFuncs = _validatorStore.Value; + IObservable validationSource = ignoreInitialError ? _checkValidation : new PrependObservable(_checkValidation, _value); + var validators = new IObservable[validatorFuncs.Count]; + for (var i = 0; i < validatorFuncs.Count; i++) + { + validators[i] = validatorFuncs[i](validationSource); + } _validationDisposable.Disposable = new ValidationStream(validators, _scheduler) .Subscribe(new DelegateObserver(x => @@ -270,12 +274,12 @@ public ReactiveProperty AddValidationError( var handler = ErrorsChanged; if (handler is not null) { - _scheduler.ScheduleOrInline(() => handler(this, SingletonDataErrorsChangedEventArgs.Value)); + _ = _scheduler.ScheduleOrInline(() => handler(this, SingletonDataErrorsChangedEventArgs.Value)); } if (lastHasErrors != currentHasErrors) { - _scheduler.ScheduleOrInline(() => + _ = _scheduler.ScheduleOrInline(() => this.RaisePropertyChanged(SingletonPropertyChangedEventArgs.HasErrors.PropertyName)); } @@ -455,7 +459,7 @@ protected virtual void Dispose(bool disposing) return; } - _disposables?.Dispose(); + _disposables.Dispose(); _checkValidation.Dispose(); _valueRefereshed.Dispose(); @@ -556,9 +560,11 @@ public void OnNext(T? value) } /// + [ExcludeFromCodeCoverage] // Never invoked: the relay subscription is disposed before its source can terminate. public void OnError(Exception error) => relay.OnError(error); /// + [ExcludeFromCodeCoverage] public void OnCompleted() => relay.OnCompleted(); } @@ -657,24 +663,54 @@ private void OnNextAt(int index, IEnumerable? value) return; } - if (Array.TrueForAll(_latest, static x => x is null)) + aggregated = BuildAggregate(); + } + + _downstream.OnNext(aggregated); + } + + /// + /// Materializes the combined error sequence from every validator's latest values while the gate is held. + /// _latest is mutated by later emissions, so a deferred query would re-evaluate against new state + /// when a downstream consumer enumerates it. A single pass splits strings from the flattened contents of + /// the remaining sequences, then both are copied once into a pre-sized result (strings first). + /// + /// The aggregated errors, or when every validator reported null. + private object?[]? BuildAggregate() + { + if (Array.TrueForAll(_latest, static x => x is null)) + { + return null; + } + + List stringValues = new(_latest.Length); + List otherValues = []; + foreach (var item in _latest) + { + if (item is null) + { + continue; + } + + if (item is string stringValue) { - aggregated = null; + stringValues.Add(stringValue); + continue; } - else + + foreach (var inner in item) { - var strings = _latest.Where(static x => x is not null).OfType(); - var others = _latest - .Where(static x => x is not null and not string) - .SelectMany(static x => x!.OfType()); - - // Materialize while holding the gate: _latest is mutated by later emissions, so a deferred - // query would re-evaluate against the new state when a downstream consumer enumerates it. - aggregated = strings.Concat(others).ToArray(); + if (inner is not null) + { + otherValues.Add(inner); + } } } - _downstream.OnNext(aggregated); + var result = new object?[stringValues.Count + otherValues.Count]; + stringValues.CopyTo(result, 0); + otherValues.CopyTo(result, stringValues.Count); + return result; } /// Forwards an error from any validator. @@ -802,14 +838,16 @@ public void OnNext(TIn value) return; } - Interlocked.Increment(ref _active); + _ = Interlocked.Increment(ref _active); _ = ForwardAsync(task); } /// + [ExcludeFromCodeCoverage] // Never invoked: the source subscription is disposed before it can terminate. public void OnError(Exception error) => Fail(error); /// + [ExcludeFromCodeCoverage] public void OnCompleted() => Done(); /// diff --git a/src/ReactiveUI.Shared/ReactiveProperty/ReactivePropertyMixins.cs b/src/ReactiveUI.Shared/ReactiveProperty/ReactivePropertyMixins.cs index 3c22f7396b..88eb5e5b1c 100644 --- a/src/ReactiveUI.Shared/ReactiveProperty/ReactivePropertyMixins.cs +++ b/src/ReactiveUI.Shared/ReactiveProperty/ReactivePropertyMixins.cs @@ -50,25 +50,20 @@ public ReactiveProperty AddValidation( var memberExpression = (MemberExpression)selfSelector.Body; var propertyInfo = (PropertyInfo)memberExpression.Member; var display = propertyInfo.GetCustomAttribute(); - var attrs = propertyInfo.GetCustomAttributes().ToArray(); + ValidationAttribute[] attrs = [.. propertyInfo.GetCustomAttributes()]; ValidationContext context = new(self, null, null) { DisplayName = display?.GetName() ?? propertyInfo.Name, - MemberName = nameof(ReactiveProperty.Value) + MemberName = nameof(ReactiveProperty<>.Value) }; if (attrs.Length != 0) { - self.AddValidationError(x => + _ = self.AddValidationError(x => { List validationResults = []; - if (Validator.TryValidateValue(x!, context, validationResults, attrs)) - { - return null; - } - - return validationResults[0].ErrorMessage; + return Validator.TryValidateValue(x!, context, validationResults, attrs) ? null : validationResults[0].ErrorMessage; }); } @@ -115,7 +110,18 @@ public void OnNext(IEnumerable? value) string? message; try { - message = value?.OfType().FirstOrDefault(); + message = null; + if (value is not null) + { + foreach (var item in value) + { + if (item is string text) + { + message = text; + break; + } + } + } } catch (Exception ex) { diff --git a/src/ReactiveUI.Shared/Routing/MessageBus.cs b/src/ReactiveUI.Shared/Routing/MessageBus.cs index 99b8447dc2..d9dc5b1193 100644 --- a/src/ReactiveUI.Shared/Routing/MessageBus.cs +++ b/src/ReactiveUI.Shared/Routing/MessageBus.cs @@ -262,7 +262,7 @@ private void WithMessageBus( block(_messageBus, item); if (_messageBus.TryGetValue(item, out var value) && !value.IsAlive) { - _messageBus.Remove(item); + _ = _messageBus.Remove(item); } } } @@ -276,7 +276,7 @@ private void WithMessageBus( /// found. private ISequencer GetScheduler((Type type, string? contract) item) { - _schedulerMappings.TryGetValue(item, out var scheduler); + _ = _schedulerMappings.TryGetValue(item, out var scheduler); return scheduler ?? Sequencer.CurrentThread; } @@ -307,33 +307,7 @@ public IDisposable Subscribe(IObserver observer) return EmptyDisposable.Instance; } - return skipFirst ? source.Subscribe(new SkipFirstObserver(observer)) : source.Subscribe(observer); - } - - /// Forwards every value except the first to the downstream observer. - /// The observer receiving values after the first. - private sealed class SkipFirstObserver(IObserver downstream) : IObserver - { - /// Whether the first value has been skipped. - private bool _skipped; - - /// - public void OnNext(T value) - { - if (!_skipped) - { - _skipped = true; - return; - } - - downstream.OnNext(value); - } - - /// - public void OnError(Exception error) => downstream.OnError(error); - - /// - public void OnCompleted() => downstream.OnCompleted(); + return skipFirst ? source.Subscribe(new SkipFirstObserver(observer)) : source.Subscribe(observer); } } } diff --git a/src/ReactiveUI.Shared/Routing/RoutingState.cs b/src/ReactiveUI.Shared/Routing/RoutingState.cs index fd9941d96c..b2d9f73f10 100644 --- a/src/ReactiveUI.Shared/Routing/RoutingState.cs +++ b/src/ReactiveUI.Shared/Routing/RoutingState.cs @@ -179,7 +179,7 @@ private void SetupRx() CurrentViewModel = new MapSignal, IRoutableViewModel>( NavigationChanges, - _ => NavigationStack.LastOrDefault()!); + _ => NavigationStack.Count > 0 ? NavigationStack[NavigationStack.Count - 1] : null!); } /// Emits a single value delivered on a scheduler. Replaces Observable.Return(value).ObserveOn(scheduler). diff --git a/src/ReactiveUI.Shared/Routing/RoutingStateMixins.cs b/src/ReactiveUI.Shared/Routing/RoutingStateMixins.cs index d697fe39f5..07f5a82117 100644 --- a/src/ReactiveUI.Shared/Routing/RoutingStateMixins.cs +++ b/src/ReactiveUI.Shared/Routing/RoutingStateMixins.cs @@ -30,7 +30,16 @@ public static class RoutingStateMixins { ArgumentExceptionHelper.ThrowIfNull(item); - return item.NavigationStack.Reverse().OfType().FirstOrDefault(); + var stack = item.NavigationStack; + for (var i = stack.Count - 1; i >= 0; i--) + { + if (stack[i] is T match) + { + return match; + } + } + + return default; } /// Gets the current view model from the top of the navigation stack. @@ -39,7 +48,8 @@ public static class RoutingStateMixins { ArgumentExceptionHelper.ThrowIfNull(item); - return item.NavigationStack.LastOrDefault(); + var stack = item.NavigationStack; + return stack.Count > 0 ? stack[stack.Count - 1] : null; } } } diff --git a/src/ReactiveUI.Shared/Routing/SkipFirstObserver.cs b/src/ReactiveUI.Shared/Routing/SkipFirstObserver.cs new file mode 100644 index 0000000000..a7393ce4de --- /dev/null +++ b/src/ReactiveUI.Shared/Routing/SkipFirstObserver.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +#if REACTIVE_SHIM +namespace ReactiveUI.Reactive; +#else +namespace ReactiveUI; +#endif + +/// Forwards every value except the first to the downstream observer. +/// The message type. +/// The observer receiving values after the first. +internal sealed class SkipFirstObserver(IObserver downstream) : IObserver +{ + /// Whether the first value has been skipped. + private bool _skipped; + + /// + public void OnNext(T value) + { + if (!_skipped) + { + _skipped = true; + return; + } + + downstream.OnNext(value); + } + + /// + public void OnError(Exception error) => downstream.OnError(error); + + /// + public void OnCompleted() => downstream.OnCompleted(); +} diff --git a/src/ReactiveUI.Shared/RxCacheSize.cs b/src/ReactiveUI.Shared/RxCacheSize.cs index 1ac4a3bbee..ab54088bb8 100644 --- a/src/ReactiveUI.Shared/RxCacheSize.cs +++ b/src/ReactiveUI.Shared/RxCacheSize.cs @@ -10,7 +10,7 @@ namespace ReactiveUI; #endif /// /// Provides configurable cache size limits for ReactiveUI's internal caching mechanisms. -/// These values can be configured via or will auto-initialize with platform-specific defaults. +/// These values can be configured via or will auto-initialize with platform-specific defaults. /// public static class RxCacheSize { @@ -81,7 +81,7 @@ internal static void Initialize(int smallCacheLimit, int bigCacheLimit) /// internal static void ResetForTesting() { - Interlocked.Exchange(ref _initialized, 0); + _ = Interlocked.Exchange(ref _initialized, 0); _smallCacheLimit = 0; _bigCacheLimit = 0; } diff --git a/src/ReactiveUI.Shared/RxSchedulers.cs b/src/ReactiveUI.Shared/RxSchedulers.cs index 1f0974ec4f..c595583b88 100644 --- a/src/ReactiveUI.Shared/RxSchedulers.cs +++ b/src/ReactiveUI.Shared/RxSchedulers.cs @@ -3,7 +3,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; #if REACTIVE_SHIM @@ -39,7 +38,7 @@ public static class RxSchedulers static RxSchedulers() { TaskpoolScheduler = TaskPoolSequencer.Default; - MainThreadScheduler ??= Sequencer.Default; + MainThreadScheduler = Sequencer.Default; } /// @@ -47,24 +46,10 @@ static RxSchedulers() /// should be run "on the UI thread". In normal mode, this will be /// DispatcherScheduler. This defaults to Sequencer.Default. /// - [SuppressMessage( - "Maintainability", - "CA1508:Avoid dead conditional code", - Justification = "Double-checked locking; another thread may set the field between the outer check and the lock.")] public static ISequencer MainThreadScheduler { - get - { - if (_mainThreadScheduler is not null) - { - return _mainThreadScheduler; - } - - lock (_lock) - { - return _mainThreadScheduler ??= Sequencer.Default; - } - } + // The static constructor assigns this field before any member is accessed, so it is never null. + get => _mainThreadScheduler!; set { @@ -79,33 +64,10 @@ public static ISequencer MainThreadScheduler /// Gets or sets the scheduler used to schedule work items to /// run in a background thread. This defaults to TaskPoolSequencer.Default. /// - [SuppressMessage( - "Maintainability", - "CA1508:Avoid dead conditional code", - Justification = "Double-checked locking; another thread may set the field between the outer check and the lock.")] public static ISequencer TaskpoolScheduler { - get - { - if (_taskpoolScheduler is not null) - { - return _taskpoolScheduler; - } - - lock (_lock) - { - if (_taskpoolScheduler is not null) - { - return _taskpoolScheduler; - } - -#if !PORTABLE - return _taskpoolScheduler ??= TaskPoolSequencer.Default; -#else - return _taskpoolScheduler ??= Sequencer.Default; -#endif - } - } + // The static constructor assigns this field before any member is accessed, so it is never null. + get => _taskpoolScheduler!; set { diff --git a/src/ReactiveUI.Shared/RxState.cs b/src/ReactiveUI.Shared/RxState.cs index 642fd3bf72..d42a45a967 100644 --- a/src/ReactiveUI.Shared/RxState.cs +++ b/src/ReactiveUI.Shared/RxState.cs @@ -59,7 +59,7 @@ internal static void InitializeExceptionHandler(IObserver exceptionHa /// internal static void ResetForTesting() { - Interlocked.Exchange(ref _exceptionHandlerInitialized, 0); + _ = Interlocked.Exchange(ref _exceptionHandlerInitialized, 0); _defaultExceptionHandler = null; } @@ -81,7 +81,7 @@ private static void InitializeDefaultExceptionHandler() Debugger.Break(); } - RxSchedulers.MainThreadScheduler.Schedule(() => throw new UnhandledErrorException( + _ = RxSchedulers.MainThreadScheduler.Schedule(() => throw new UnhandledErrorException( "An object implementing IHandleObservableErrors (often a ReactiveCommand or ObservableAsPropertyHelper) has errored," + " thereby breaking its observable pipeline. To prevent this, ensure the pipeline does not error, or Subscribe to the " + "ThrownExceptions property of the object in question to handle the erroneous case.", diff --git a/src/ReactiveUI.Shared/RxSuspension.cs b/src/ReactiveUI.Shared/RxSuspension.cs index f70de0b9c7..a45dd6d154 100644 --- a/src/ReactiveUI.Shared/RxSuspension.cs +++ b/src/ReactiveUI.Shared/RxSuspension.cs @@ -61,7 +61,7 @@ internal static void InitializeSuspensionHost(ISuspensionHost suspensionHost) /// internal static void ResetForTesting() { - Interlocked.Exchange(ref _suspensionHostInitialized, 0); + _ = Interlocked.Exchange(ref _suspensionHostInitialized, 0); _suspensionHost = null; } diff --git a/src/ReactiveUI.Shared/Scheduler/ScheduledSubject.cs b/src/ReactiveUI.Shared/Scheduler/ScheduledSubject.cs index 5ebb34f4a7..7f38182381 100644 --- a/src/ReactiveUI.Shared/Scheduler/ScheduledSubject.cs +++ b/src/ReactiveUI.Shared/Scheduler/ScheduledSubject.cs @@ -97,7 +97,7 @@ public IDisposable Subscribe(IObserver observer) ArgumentExceptionHelper.ThrowIfNull(observer); Interlocked.Exchange(ref _defaultObserverSub, EmptyDisposable.Instance).Dispose(); - Interlocked.Increment(ref _observerRefCount); + _ = Interlocked.Increment(ref _observerRefCount); var inner = _subject.Subscribe(new SchedulingObserver(observer, _scheduler)); return new Subscription(this, inner); diff --git a/src/ReactiveUI.Shared/Scheduler/WaitForDispatcherScheduler.cs b/src/ReactiveUI.Shared/Scheduler/WaitForDispatcherScheduler.cs index 9738b721aa..e4247d54ee 100644 --- a/src/ReactiveUI.Shared/Scheduler/WaitForDispatcherScheduler.cs +++ b/src/ReactiveUI.Shared/Scheduler/WaitForDispatcherScheduler.cs @@ -29,7 +29,7 @@ public WaitForDispatcherScheduler(Func schedulerFactory) { _schedulerFactory = schedulerFactory; - AttemptToCreateScheduler(); + _ = AttemptToCreateScheduler(); } /// diff --git a/src/ReactiveUI.Shared/Suspension/SuspensionHostExtensions.cs b/src/ReactiveUI.Shared/Suspension/SuspensionHostExtensions.cs index a86e17231a..8a47cdbab7 100644 --- a/src/ReactiveUI.Shared/Suspension/SuspensionHostExtensions.cs +++ b/src/ReactiveUI.Shared/Suspension/SuspensionHostExtensions.cs @@ -500,7 +500,7 @@ public void OnNext(TArg value) return; } - op.Subscribe(new ResultObserver(logHost, value, onFinally, successMessage, errorMessage)); + _ = op.Subscribe(new ResultObserver(logHost, value, onFinally, successMessage, errorMessage)); } /// diff --git a/src/ReactiveUI.Shared/Suspension/SuspensionHost{TAppState}.cs b/src/ReactiveUI.Shared/Suspension/SuspensionHost{TAppState}.cs index 37dfcd7128..880f4805ec 100644 --- a/src/ReactiveUI.Shared/Suspension/SuspensionHost{TAppState}.cs +++ b/src/ReactiveUI.Shared/Suspension/SuspensionHost{TAppState}.cs @@ -189,7 +189,7 @@ public TAppState? AppStateValue get => _appState; set { - this.RaiseAndSetIfChanged(ref _appState, value); + _ = this.RaiseAndSetIfChanged(ref _appState, value); _appStateValueChanged.OnNext(value); } diff --git a/src/ReactiveUI.Testing/RxTest.cs b/src/ReactiveUI.Testing/RxTest.cs index 4d85dcff2c..72ad03363b 100644 --- a/src/ReactiveUI.Testing/RxTest.cs +++ b/src/ReactiveUI.Testing/RxTest.cs @@ -64,7 +64,7 @@ public static async Task AppBuilderTestAsync(Func testBody, int maxWaitMs) } finally { - TestGate.Release(); + _ = TestGate.Release(); } } } diff --git a/src/ReactiveUI.WinUI.Reactive/ReactiveUI.WinUI.Reactive.csproj b/src/ReactiveUI.WinUI.Reactive/ReactiveUI.WinUI.Reactive.csproj index d24d474590..34b87b43c4 100644 --- a/src/ReactiveUI.WinUI.Reactive/ReactiveUI.WinUI.Reactive.csproj +++ b/src/ReactiveUI.WinUI.Reactive/ReactiveUI.WinUI.Reactive.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj b/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj index ae5f3d8f60..d1ee38c9ad 100644 --- a/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj +++ b/src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/ReactiveUI.Winforms.Reactive/ReactiveUI.Winforms.Reactive.csproj b/src/ReactiveUI.Winforms.Reactive/ReactiveUI.Winforms.Reactive.csproj index 881e8e8700..bfe3292405 100644 --- a/src/ReactiveUI.Winforms.Reactive/ReactiveUI.Winforms.Reactive.csproj +++ b/src/ReactiveUI.Winforms.Reactive/ReactiveUI.Winforms.Reactive.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs b/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs index a5c8cb59d4..b9ea756f6d 100644 --- a/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs +++ b/src/ReactiveUI.Winforms/CreatesWinformsCommandBinding.cs @@ -119,30 +119,33 @@ public IDisposable? BindCommandToObject< const BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy; var type = typeof(T); - var eventInfo = _defaultEventsToBind - .Select(x => new { EventInfo = type.GetEvent(x.name, BindingFlags), Args = x.type }) - .FirstOrDefault(x => x.EventInfo is not null); + EventInfo? matchedEvent = null; + Type? matchedArgs = null; + foreach (var (name, argType) in _defaultEventsToBind) + { + var candidate = type.GetEvent(name, BindingFlags); + if (candidate is not null) + { + matchedEvent = candidate; + matchedArgs = argType; + break; + } + } - if (eventInfo is null) + if (matchedEvent is null) { return null; } // Dynamically call the correct generic method based on event args type - if (eventInfo.Args == typeof(EventArgs)) + if (matchedArgs == typeof(EventArgs)) { - return BindCommandToObject(command, target, commandParameter, eventInfo.EventInfo?.Name!); - } - else if (eventInfo.Args == typeof(MouseEventArgs)) - { - return BindCommandToObject( - command, - target, - commandParameter, - eventInfo.EventInfo?.Name!); + return BindCommandToObject(command, target, commandParameter, matchedEvent.Name); } - return null; + return matchedArgs == typeof(MouseEventArgs) + ? BindCommandToObject(command, target, commandParameter, matchedEvent.Name) + : null; } /// diff --git a/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj b/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj index 83f516679c..ed27ef1711 100644 --- a/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj +++ b/src/ReactiveUI.Winforms/ReactiveUI.Winforms.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/ReactiveUI.Winforms/RoutedViewHost.cs b/src/ReactiveUI.Winforms/RoutedViewHost.cs index 2218df3e0c..8512307c76 100644 --- a/src/ReactiveUI.Winforms/RoutedViewHost.cs +++ b/src/ReactiveUI.Winforms/RoutedViewHost.cs @@ -167,7 +167,7 @@ private void OnViewModelContract(IRoutableViewModel? viewModel, string contract) { if (_host.DefaultContent is not null) { - InitView(_host.DefaultContent); + _ = InitView(_host.DefaultContent); _host.Controls.Add(_host.DefaultContent); } diff --git a/src/ReactiveUI.Wpf.Reactive/ReactiveUI.Wpf.Reactive.csproj b/src/ReactiveUI.Wpf.Reactive/ReactiveUI.Wpf.Reactive.csproj index f5977ffdbd..6f9c7dee04 100644 --- a/src/ReactiveUI.Wpf.Reactive/ReactiveUI.Wpf.Reactive.csproj +++ b/src/ReactiveUI.Wpf.Reactive/ReactiveUI.Wpf.Reactive.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/ReactiveUI.Wpf.Shared/ActivationForViewFetcher.cs b/src/ReactiveUI.Wpf.Shared/ActivationForViewFetcher.cs index ba81eae71f..d5c673afbb 100644 --- a/src/ReactiveUI.Wpf.Shared/ActivationForViewFetcher.cs +++ b/src/ReactiveUI.Wpf.Shared/ActivationForViewFetcher.cs @@ -63,12 +63,7 @@ public IObservable GetActivationForView(IActivatableView view) /// An observable that emits activation state for the window. private static IObservable GetActivationForWindow(IActivatableView view) { - if (view is not Window window) - { - return Signal.None(); - } - - return new FromEventObservable(onNext => + return view is not Window window ? Signal.None() : new FromEventObservable(onNext => { void Handler(object? sender, EventArgs e) => onNext(false); window.Closed += Handler; diff --git a/src/ReactiveUI.Wpf.Shared/Binding/ValidationBindingWpf.cs b/src/ReactiveUI.Wpf.Shared/Binding/ValidationBindingWpf.cs index c349c4774a..3f7c93a8ae 100644 --- a/src/ReactiveUI.Wpf.Shared/Binding/ValidationBindingWpf.cs +++ b/src/ReactiveUI.Wpf.Shared/Binding/ValidationBindingWpf.cs @@ -9,6 +9,7 @@ using System.Windows.Data; using System.Windows.Markup.Primitives; using System.Windows.Media; +using Expression = System.Linq.Expressions.Expression; #if REACTIVE_SHIM namespace ReactiveUI.Reactive.Wpf.Binding; @@ -66,35 +67,36 @@ public ValidationBindingWpf( // Get the View details View = view; ViewExpression = Reflection.Rewrite(viewProperty.Body); - var viewExpressionChain = ViewExpression.GetExpressionChain().ToArray(); + Expression[] viewExpressionChain = [.. ViewExpression.GetExpressionChain()]; var controlName = ExtractControlName(viewExpressionChain, typeof(TView)); _control = FindControlByName(view as DependencyObject, controlName) ?? throw new ArgumentException( - $"Control '{controlName}' not found in view {typeof(TView).Name}", + $"Control '{controlName}' not found in view {nameof(TView)}", nameof(viewProperty)); - var propertyName = viewExpressionChain.LastOrDefault()?.GetMemberInfo()?.Name; + var lastViewExpression = viewExpressionChain.Length > 0 ? viewExpressionChain[^1] : null; + var propertyName = lastViewExpression?.GetMemberInfo()?.Name; _dependencyProperty = GetDependencyProperty(_control, propertyName) ?? throw new ArgumentException( - $"Dependency property '{propertyName}' not found on {typeof(TVProp).Name}", + $"Dependency property '{propertyName}' not found on {nameof(TVProp)}", nameof(viewProperty)); Changed = new ChangedObservable( Reflection.ViewModelWhenAnyValue(viewModel, view, ViewModelExpression), view.WhenAnyDynamic(ViewExpression, static x => (TVProp?)x.Value)); Direction = BindingDirection.TwoWay; - Bind(); + _ = Bind(); } /// Gets the expression representing the view model property being bound. - public System.Linq.Expressions.Expression ViewModelExpression { get; } + public Expression ViewModelExpression { get; } /// Gets the view instance that owns this binding. public TView View { get; } /// Gets the expression representing the view property being bound. - public System.Linq.Expressions.Expression ViewExpression { get; } + public Expression ViewExpression { get; } /// Gets an observable that emits values when either the view model or view property changes. public IObservable Changed { get; } @@ -106,7 +108,7 @@ public ValidationBindingWpf( /// A disposable that can be used to remove the binding. public IDisposable Bind() { - _control.SetBinding( + _ = _control.SetBinding( _dependencyProperty, new System.Windows.Data.Binding { @@ -134,12 +136,17 @@ public void Dispose() /// /// The expression to extract the path from. /// The dot-separated property path. - internal static string ExtractPropertyPath(System.Linq.Expressions.Expression expression) + internal static string ExtractPropertyPath(Expression expression) { - var chain = expression.GetExpressionChain(); - var pathParts = chain - .Select(static x => x.GetMemberInfo()?.Name) - .Where(static name => !string.IsNullOrEmpty(name)); + List pathParts = []; + foreach (var node in expression.GetExpressionChain()) + { + var name = node.GetMemberInfo()?.Name; + if (name is { Length: > 0 }) + { + pathParts.Add(name); + } + } return string.Join(".", pathParts); } @@ -152,7 +159,7 @@ internal static string ExtractPropertyPath(System.Linq.Expressions.Expression ex /// The type of the view for error messages. /// The name of the control. /// Thrown when the control name cannot be determined. - internal static string ExtractControlName(System.Linq.Expressions.Expression[] expressionChain, Type viewType) + internal static string ExtractControlName(Expression[] expressionChain, Type viewType) { if (expressionChain.Length < 2) { @@ -241,9 +248,23 @@ internal static IEnumerable EnumerateAttachedProperties(obje } } - return EnumerateDependencyProperties(element) - .Concat(EnumerateAttachedProperties(element)) - .FirstOrDefault(x => x.Name == name); + foreach (var property in EnumerateDependencyProperties(element)) + { + if (property.Name == name) + { + return property; + } + } + + foreach (var property in EnumerateAttachedProperties(element)) + { + if (property.Name == name) + { + return property; + } + } + + return null; } /// Finds the first control with the specified name in the visual tree. @@ -252,17 +273,13 @@ internal static IEnumerable EnumerateAttachedProperties(obje /// The first matching FrameworkElement, or null if not found. internal static FrameworkElement? FindControlByName(DependencyObject? parent, string? name) { - if (parent is null) - { - return null; - } - - if (name is null || string.IsNullOrWhiteSpace(name)) + if (parent is null || name is null || string.IsNullOrWhiteSpace(name)) { return null; } - return FindControlsByNameIterator(parent, name).FirstOrDefault(); + using var enumerator = FindControlsByNameIterator(parent, name).GetEnumerator(); + return enumerator.MoveNext() ? enumerator.Current : null; } /// Releases the resources used by the binding. diff --git a/src/ReactiveUI.Wpf.Shared/Common/AutoDataTemplateBindingHook.cs b/src/ReactiveUI.Wpf.Shared/Common/AutoDataTemplateBindingHook.cs index 77f7668a3d..6a1e6f6d6b 100644 --- a/src/ReactiveUI.Wpf.Shared/Common/AutoDataTemplateBindingHook.cs +++ b/src/ReactiveUI.Wpf.Shared/Common/AutoDataTemplateBindingHook.cs @@ -48,7 +48,7 @@ public bool ExecuteHook( ArgumentExceptionHelper.ThrowIfNull(getCurrentViewProperties); var viewProperties = getCurrentViewProperties(); - var lastViewProperty = viewProperties.LastOrDefault(); + var lastViewProperty = viewProperties.Length > 0 ? viewProperties[^1] : null; if (lastViewProperty?.Sender is not ItemsControl itemsControl) { diff --git a/src/ReactiveUI.Wpf.Shared/Common/RoutedViewHost.cs b/src/ReactiveUI.Wpf.Shared/Common/RoutedViewHost.cs index b0786df78c..10e558dbe8 100644 --- a/src/ReactiveUI.Wpf.Shared/Common/RoutedViewHost.cs +++ b/src/ReactiveUI.Wpf.Shared/Common/RoutedViewHost.cs @@ -108,7 +108,7 @@ public RoutedViewHost() // NB: The DistinctUntilChanged is useful because most views in // WinRT will end up getting here twice - once for configuring // the RoutedViewHost's ViewModel, and once on load via SizeChanged - this.WhenActivated(d => + _ = this.WhenActivated(d => d(viewModelAndContract.DistinctUntilChanged() .Subscribe(new DelegateObserver<(IRoutableViewModel? viewModel, string? contract)>( ResolveViewForViewModel, diff --git a/src/ReactiveUI.Wpf.Shared/Common/ViewModelViewHost.cs b/src/ReactiveUI.Wpf.Shared/Common/ViewModelViewHost.cs index 9b861c0bf5..014beb0d96 100644 --- a/src/ReactiveUI.Wpf.Shared/Common/ViewModelViewHost.cs +++ b/src/ReactiveUI.Wpf.Shared/Common/ViewModelViewHost.cs @@ -104,7 +104,7 @@ public ViewModelViewHost() viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)); - this.WhenActivated(d => + _ = this.WhenActivated(d => { d(new ObserveOnObservable(contractChanged, RxSchedulers.MainThreadScheduler) .Subscribe(new DelegateObserver(x => _viewContract = x ?? string.Empty))); diff --git a/src/ReactiveUI.Wpf.Shared/TransitioningContentControl.cs b/src/ReactiveUI.Wpf.Shared/TransitioningContentControl.cs index a833928f34..7c72fad9fc 100644 --- a/src/ReactiveUI.Wpf.Shared/TransitioningContentControl.cs +++ b/src/ReactiveUI.Wpf.Shared/TransitioningContentControl.cs @@ -407,10 +407,20 @@ public override void OnApplyTemplate() if (VisualStateManager.GetVisualStateGroups(Container) is IEnumerable groups) { - PresentationStateGroup = groups.FirstOrDefault(static o => o.Name == PresentationGroup); + VisualStateGroup? matchedGroup = null; + foreach (var group in groups) + { + if (group.Name == PresentationGroup) + { + matchedGroup = group; + break; + } + } + + PresentationStateGroup = matchedGroup; } - VisualStateManager.GoToState(this, NormalState, false); + _ = VisualStateManager.GoToState(this, NormalState, false); } /// Gets the DPI scale for the specified UI element. @@ -598,10 +608,15 @@ internal Storyboard GetTransitionStoryboardByName(string transitionName) "Visual state group is not initialized or states collection is invalid."); } - var transition = states - .Where(o => o.Name == transitionName) - .Select(o => o.Storyboard) - .FirstOrDefault(); + Storyboard? transition = null; + foreach (var state in states) + { + if (state.Name == transitionName) + { + transition = state.Storyboard; + break; + } + } return transition ?? throw new InvalidOperationException($"Transition '{transitionName}' not found in visual state group."); @@ -663,7 +678,7 @@ protected override void OnContentChanged(object oldContent, object newContent) private void AbortTransition() { // Go to a normal state and release our hold on the old content. - VisualStateManager.GoToState(this, NormalState, false); + _ = VisualStateManager.GoToState(this, NormalState, false); _isTransitioning = false; if (PreviousImageSite is null) @@ -741,7 +756,7 @@ private void QueueTransition(object newContent) RaiseTransitionStarted(); statesRemaining--; - VisualStateManager.GoToState(this, startingTransitionName, false); + _ = VisualStateManager.GoToState(this, startingTransitionName, false); void NextState(object? o, EventArgs e) { @@ -752,7 +767,7 @@ void NextState(object? o, EventArgs e) } statesRemaining--; - VisualStateManager.GoToState(this, transitionInName, false); + _ = VisualStateManager.GoToState(this, transitionInName, false); } } } diff --git a/src/ReactiveUI.Wpf.Shared/WpfCommandRebindingCustomizer.cs b/src/ReactiveUI.Wpf.Shared/WpfCommandRebindingCustomizer.cs index 18c30a7ba7..8399507924 100644 --- a/src/ReactiveUI.Wpf.Shared/WpfCommandRebindingCustomizer.cs +++ b/src/ReactiveUI.Wpf.Shared/WpfCommandRebindingCustomizer.cs @@ -52,7 +52,7 @@ public bool TryUpdateCommand(TControl? control, ICommand? command) // control off-thread (which would throw an InvalidOperationException). if (control is DispatcherObject dispatcherObject && !dispatcherObject.CheckAccess()) { - RxSchedulers.MainThreadScheduler.Schedule( + _ = RxSchedulers.MainThreadScheduler.Schedule( (Property: commandProperty, Control: control, Command: command), static (_, state) => { diff --git a/src/ReactiveUI.Wpf.Shared/WpfPropertyBinderImplementation.cs b/src/ReactiveUI.Wpf.Shared/WpfPropertyBinderImplementation.cs index a96a2d93db..577f88a122 100644 --- a/src/ReactiveUI.Wpf.Shared/WpfPropertyBinderImplementation.cs +++ b/src/ReactiveUI.Wpf.Shared/WpfPropertyBinderImplementation.cs @@ -34,7 +34,7 @@ protected override void SetViewValue(TView view, Action setter) return; } - RxSchedulers.MainThreadScheduler.Schedule( + _ = RxSchedulers.MainThreadScheduler.Schedule( setter, static (_, state) => { diff --git a/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj b/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj index f558e43cad..85bb0dd5eb 100644 --- a/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj +++ b/src/ReactiveUI.Wpf/ReactiveUI.Wpf.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs b/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs index 5e7308e9b7..5132403aa5 100644 --- a/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs +++ b/src/ReactiveUI/Platforms/android/AndroidObservableForWidgets.cs @@ -263,15 +263,10 @@ private static DispatchItem CreateFromAdapterView() [SupportedOSPlatform("android35.0")] private static DispatchItem CreateTimePickerHourFromWidget() { - if ((int)Build.VERSION.SdkInt >= TimePickerHourMinuteApiLevel) - { - return CreateFromWidget( + return (int)Build.VERSION.SdkInt >= TimePickerHourMinuteApiLevel ? CreateFromWidget( static v => v.Hour, static (v, h) => v.TimeChanged += h, - static (v, h) => v.TimeChanged -= h); - } - - return CreateFromWidget( + static (v, h) => v.TimeChanged -= h) : CreateFromWidget( static v => v.CurrentHour, static (v, h) => v.TimeChanged += h, static (v, h) => v.TimeChanged -= h); @@ -287,15 +282,10 @@ private static DispatchItem CreateTimePickerHourFromWidget() [SupportedOSPlatform("android35.0")] private static DispatchItem CreateTimePickerMinuteFromWidget() { - if ((int)Build.VERSION.SdkInt >= TimePickerHourMinuteApiLevel) - { - return CreateFromWidget( + return (int)Build.VERSION.SdkInt >= TimePickerHourMinuteApiLevel ? CreateFromWidget( static v => v.Minute, static (v, h) => v.TimeChanged += h, - static (v, h) => v.TimeChanged -= h); - } - - return CreateFromWidget( + static (v, h) => v.TimeChanged -= h) : CreateFromWidget( static v => v.CurrentMinute, static (v, h) => v.TimeChanged += h, static (v, h) => v.TimeChanged -= h); diff --git a/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs b/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs index 397b3b0761..e2d5bd5d93 100644 --- a/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs +++ b/src/ReactiveUI/Platforms/android/AutoSuspendHelper.cs @@ -73,8 +73,8 @@ public AutoSuspendHelper(Application hostApplication) hostApplication.RegisterActivityLifecycleCallbacks(new ObservableLifecycle(this)); // Both create and save-instance-state callbacks update the latest bundle (replaces a Merge + Subscribe). - _onCreate.Subscribe(new DelegateObserver(static x => LatestBundle = x)); - _onSaveInstanceState.Subscribe(new DelegateObserver(static x => LatestBundle = x)); + _ = _onCreate.Subscribe(new DelegateObserver(static x => LatestBundle = x)); + _ = _onSaveInstanceState.Subscribe(new DelegateObserver(static x => LatestBundle = x)); RxSuspension.SuspensionHost.IsLaunchingNew = new CreateSignalObservable(_onCreate, emitWhenNull: true); RxSuspension.SuspensionHost.IsResuming = new CreateSignalObservable(_onCreate, emitWhenNull: false); diff --git a/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs b/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs index ecb1180705..2331863ba6 100644 --- a/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs +++ b/src/ReactiveUI/Platforms/android/BundleSuspensionDriver.cs @@ -43,13 +43,8 @@ public sealed class BundleSuspensionDriver : ISuspensionDriver } var buffer = AutoSuspendHelper.LatestBundle.GetByteArray(StateKey); - if (buffer is null) - { - return Signal.Fail( - new InvalidOperationException("The persisted state buffer could not be found.")); - } - - return new TaskObservable(DeserializeAsync()); + return buffer is null ? Signal.Fail( + new InvalidOperationException("The persisted state buffer could not be found.")) : new TaskObservable(DeserializeAsync()); async Task DeserializeAsync() { @@ -77,13 +72,8 @@ public sealed class BundleSuspensionDriver : ISuspensionDriver } var buffer = AutoSuspendHelper.LatestBundle.GetByteArray(StateKey); - if (buffer is null) - { - return Signal.Fail( - new InvalidOperationException("The persisted state buffer could not be found.")); - } - - return new TaskObservable(DeserializeAsync()); + return buffer is null ? Signal.Fail( + new InvalidOperationException("The persisted state buffer could not be found.")) : new TaskObservable(DeserializeAsync()); async Task DeserializeAsync() { diff --git a/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs b/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs index 1a08b3eece..0261df42b5 100644 --- a/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs +++ b/src/ReactiveUI/Platforms/android/FlexibleCommandBinder.cs @@ -43,10 +43,16 @@ public int GetAffinityForObject< return 0; } - var match = _config.Keys - .Where(x => x.IsAssignableFrom(typeof(T))) - .OrderByDescending(x => _config[x].Affinity) - .FirstOrDefault(); + Type? match = null; + var bestAffinity = int.MinValue; + foreach (var key in _config.Keys) + { + if (key.IsAssignableFrom(typeof(T)) && _config[key].Affinity > bestAffinity) + { + bestAffinity = _config[key].Affinity; + match = key; + } + } if (match is null) { @@ -70,10 +76,22 @@ public IDisposable? BindCommandToObject< var type = target.GetType(); - var match = _config.Keys - .Where(x => x.IsAssignableFrom(type)) - .OrderByDescending(x => _config[x].Affinity) - .FirstOrDefault() ?? throw new NotSupportedException($"CommandBinding for {type.Name} is not supported"); + Type? match = null; + var bestAffinity = int.MinValue; + foreach (var key in _config.Keys) + { + if (key.IsAssignableFrom(type) && _config[key].Affinity > bestAffinity) + { + bestAffinity = _config[key].Affinity; + match = key; + } + } + + if (match is null) + { + throw new NotSupportedException($"CommandBinding for {type.Name} is not supported"); + } + var typeProperties = _config[match]; return typeProperties.CreateBinding?.Invoke(command, target, commandParameter) ?? EmptyDisposable.Instance; diff --git a/src/ReactiveUI/Platforms/android/ObjectExtensions.cs b/src/ReactiveUI/Platforms/android/ObjectExtensions.cs index de9bc1c63b..a9419fe45c 100644 --- a/src/ReactiveUI/Platforms/android/ObjectExtensions.cs +++ b/src/ReactiveUI/Platforms/android/ObjectExtensions.cs @@ -66,12 +66,7 @@ public TObject ToNetObject() /// A Java-compatible object that represents the specified value, or null if the value is null. public Object? ToJavaObject() { - if (value is null) - { - return null; - } - - return new JavaHolder(value); + return value is null ? null : new JavaHolder(value); } } } diff --git a/src/ReactiveUI/Platforms/android/ReactiveActivity.cs b/src/ReactiveUI/Platforms/android/ReactiveActivity.cs index 6f57d90439..038a4e09a2 100644 --- a/src/ReactiveUI/Platforms/android/ReactiveActivity.cs +++ b/src/ReactiveUI/Platforms/android/ReactiveActivity.cs @@ -185,7 +185,7 @@ public void OnNext((int requestCode, Result resultCode, Intent? intent) value) return; } - _completion.TrySetResult((value.resultCode, value.intent)); + _ = _completion.TrySetResult((value.resultCode, value.intent)); Dispose(); } @@ -197,7 +197,7 @@ public void OnError(Exception error) return; } - _completion.TrySetException(error); + _ = _completion.TrySetException(error); Dispose(); } @@ -209,7 +209,7 @@ public void OnCompleted() return; } - _completion.TrySetCanceled(); + _ = _completion.TrySetCanceled(); Dispose(); } diff --git a/src/ReactiveUI/Platforms/android/UsbManagerExtensions.cs b/src/ReactiveUI/Platforms/android/UsbManagerExtensions.cs index 95422b654b..991119a9fb 100644 --- a/src/ReactiveUI/Platforms/android/UsbManagerExtensions.cs +++ b/src/ReactiveUI/Platforms/android/UsbManagerExtensions.cs @@ -62,7 +62,7 @@ public IDisposable Subscribe(IObserver observer) ArgumentExceptionHelper.ThrowIfNull(observer); UsbDevicePermissionReceiver usbPermissionReceiver = new(observer, device); - context.RegisterReceiver(usbPermissionReceiver, new(ActionUsbPermission)); + _ = context.RegisterReceiver(usbPermissionReceiver, new(ActionUsbPermission)); var intent = PendingIntent.GetBroadcast(context, 0, new(ActionUsbPermission), 0); manager.RequestPermission(device, intent); @@ -89,7 +89,7 @@ public IDisposable Subscribe(IObserver observer) ArgumentExceptionHelper.ThrowIfNull(observer); UsbAccessoryPermissionReceiver usbPermissionReceiver = new(observer, accessory); - context.RegisterReceiver(usbPermissionReceiver, new(ActionUsbPermission)); + _ = context.RegisterReceiver(usbPermissionReceiver, new(ActionUsbPermission)); var intent = PendingIntent.GetBroadcast(context, 0, new(ActionUsbPermission), 0); manager.RequestPermission(accessory, intent); diff --git a/src/ReactiveUI/Platforms/android/ViewMixins.cs b/src/ReactiveUI/Platforms/android/ViewMixins.cs index fc6996e188..fa7ba8cfad 100644 --- a/src/ReactiveUI/Platforms/android/ViewMixins.cs +++ b/src/ReactiveUI/Platforms/android/ViewMixins.cs @@ -36,12 +36,7 @@ public T GetViewHost() where T : ILayoutViewHost { var tagData = item?.GetTag(ViewHostTag); - if (tagData is not null) - { - return tagData.ToNetObject(); - } - - return default!; + return tagData is not null ? tagData.ToNetObject() : default!; } /// Retrieves the layout view host associated with the specified view, if one exists. diff --git a/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs b/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs index f7ee4f2827..8f1ce6c4db 100644 --- a/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs +++ b/src/ReactiveUI/Platforms/apple-common/AppSupportJsonSuspensionDriver.cs @@ -63,7 +63,7 @@ public AppSupportJsonSuspensionDriver(string subDirectory) { ArgumentExceptionHelper.ThrowIfNull(subDirectory); - _appDirectory = new Lazy( + _appDirectory = new( () => CreateAppDirectory(NSSearchPathDirectory.ApplicationSupportDirectory, subDirectory), isThreadSafe: true); } @@ -173,11 +173,7 @@ private static string CreateAppDirectory(NSSearchPathDirectory targetDir, string // Allocate NSFileManager only once per driver instance via Lazy. Kept local and simple. var fm = new NSFileManager(); - var url = fm.GetUrl(targetDir, NSSearchPathDomain.All, null, true, out _); - if (url is null) - { - throw new InvalidOperationException("Unable to resolve platform application support directory."); - } + var url = fm.GetUrl(targetDir, NSSearchPathDomain.All, null, true, out _) ?? throw new InvalidOperationException("Unable to resolve platform application support directory."); var bundleId = NSBundle.MainBundle?.BundleIdentifier; if (string.IsNullOrEmpty(bundleId)) @@ -195,7 +191,7 @@ private static string CreateAppDirectory(NSSearchPathDirectory targetDir, string if (!Directory.Exists(path)) { - Directory.CreateDirectory(path); + _ = Directory.CreateDirectory(path); } return path; diff --git a/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeOffsetConverter.cs b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeOffsetConverter.cs index bf651ee330..694a39b92b 100644 --- a/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeOffsetConverter.cs +++ b/src/ReactiveUI/Platforms/apple-common/Converters/NSDateToDateTimeOffsetConverter.cs @@ -27,7 +27,7 @@ public override bool TryConvert(NSDate? from, object? conversionHint, [NotNullWh return false; } - result = new DateTimeOffset((DateTime)from); + result = new((DateTime)from); return true; } } diff --git a/src/ReactiveUI/Platforms/apple-common/IndexNormalizer.cs b/src/ReactiveUI/Platforms/apple-common/IndexNormalizer.cs index 9edb168968..d07aa18c25 100644 --- a/src/ReactiveUI/Platforms/apple-common/IndexNormalizer.cs +++ b/src/ReactiveUI/Platforms/apple-common/IndexNormalizer.cs @@ -24,12 +24,19 @@ public static class IndexNormalizer /// A list updates. public static IList Normalize(IEnumerable updates) { - var updatesList = updates.ToList(); + List updatesList = [.. updates]; MarkDuplicates(updatesList); - return [.. updatesList - .Select((x, i) => x.IsDuplicate ? null : Update.Create(x.Type, CalculateUpdateIndex(updatesList, i))) - .Where(x => x is not null)]; + List result = []; + for (var i = 0; i < updatesList.Count; i++) + { + if (!updatesList[i].IsDuplicate) + { + result.Add(Update.Create(updatesList[i].Type, CalculateUpdateIndex(updatesList, i))); + } + } + + return result; } /// diff --git a/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs b/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs index f61de1c116..013b49f07d 100644 --- a/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs +++ b/src/ReactiveUI/Platforms/apple-common/KVOObservableForProperty.cs @@ -190,29 +190,17 @@ private static bool IsDeclaredOnNSObject( private static string GetCocoaKeyPathUnsafe(Type senderType, string propertyName) { // Note: This logic preserves the original behavior pattern: best-effort attempt and fallback. - var property = senderType - .GetTypeInfo() - .DeclaredProperties - .FirstOrDefault(p => !p.IsStatic()); - + var property = FindFirstInstanceProperty(senderType); var propIsBoolean = false; if (property is not null) { propIsBoolean = property.PropertyType == typeof(bool); - var getter = property.GetGetMethod(); - if (getter is not null) + var export = FindExportAttribute(property.GetGetMethod()); + if (export?.Selector is not null) { - var export = getter - .GetCustomAttributes(inherit: true) - .OfType() - .FirstOrDefault(); - - if (export?.Selector is not null) - { - return export.Selector; - } + return export.Selector; } } @@ -226,6 +214,45 @@ private static string GetCocoaKeyPathUnsafe(Type senderType, string propertyName propertyName.AsSpan(1)); } + /// Returns the first non-static declared property of the type, or . + /// The runtime type to inspect. + /// The first instance property, or when none exist. + [RequiresUnreferencedCode("Uses reflection over runtime types which is not trim- or AOT-safe.")] + private static PropertyInfo? FindFirstInstanceProperty(Type senderType) + { + foreach (var candidate in senderType.GetTypeInfo().DeclaredProperties) + { + if (!candidate.IsStatic()) + { + return candidate; + } + } + + return null; + } + + /// Returns the first applied to the getter, or . + /// The property getter, which may be . + /// The export attribute, or when none is present. + [RequiresUnreferencedCode("Uses reflection over runtime members which is not trim- or AOT-safe.")] + private static ExportAttribute? FindExportAttribute(MethodInfo? getter) + { + if (getter is null) + { + return null; + } + + foreach (var attribute in getter.GetCustomAttributes(inherit: true)) + { + if (attribute is ExportAttribute exportAttribute) + { + return exportAttribute; + } + } + + return null; + } + /// /// A fused that wires NSObject Key-Value Observing (KVO) add/remove /// observer calls directly inside , replacing the previous diff --git a/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs b/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs index 9f64081129..3d0c7be350 100644 --- a/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs +++ b/src/ReactiveUI/Platforms/apple-common/ObservableForPropertyBase.cs @@ -115,12 +115,7 @@ public int GetAffinityForObject(Type? type, string propertyName, bool beforeChan } var type = sender.GetType(); - var match = ResolveBestMatch(type, propertyName); - - if (match is null) - { - throw new NotSupportedException($"Notifications for {type.Name}.{propertyName} are not supported"); - } + var match = ResolveBestMatch(type, propertyName) ?? throw new NotSupportedException($"Notifications for {type.Name}.{propertyName} are not supported"); // Do not invoke user-provided observable factories under lock. return match.CreateObservable.Invoke((NSObject)sender, expression); @@ -226,7 +221,7 @@ protected void Register( _config[type] = typeProperties; } - typeProperties[property] = new ObservablePropertyInfo(affinity, createObservable); + typeProperties[property] = new(affinity, createObservable); // Invalidate caches by bumping version. _version++; @@ -277,7 +272,7 @@ protected void Register( } // Publish computed value to cache (including null, to avoid repeated scans for unsupported properties). - _bestMatchCache[key] = new CacheEntry(versionSnapshot, best); + _bestMatchCache[key] = new(versionSnapshot, best); return best; } diff --git a/src/ReactiveUI/Platforms/apple-common/UIViewControllerMixins.cs b/src/ReactiveUI/Platforms/apple-common/UIViewControllerMixins.cs index 677e83785f..8a7bb6e6c1 100644 --- a/src/ReactiveUI/Platforms/apple-common/UIViewControllerMixins.cs +++ b/src/ReactiveUI/Platforms/apple-common/UIViewControllerMixins.cs @@ -24,7 +24,7 @@ internal static class UIViewControllerMixins { /// Recursively activates or deactivates all subviews of the given view. /// to activate subviews; to deactivate. - private void ActivateSubviews(bool activate) + internal void ActivateSubviews(bool activate) { ArgumentExceptionHelper.ThrowIfNull(masterView); diff --git a/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs b/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs index b7533a9ead..e5bcf9fae8 100644 --- a/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs +++ b/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs @@ -74,9 +74,9 @@ public class ViewModelViewHost : ReactiveViewController /// Initializes a new instance of the class. public ViewModelViewHost() { - _currentView = new SwapDisposable(); - _subscriptions = new DisposableBag(); - _viewContractObservableSubscription = new SwapDisposable(); + _currentView = new(); + _subscriptions = new(); + _viewContractObservableSubscription = new(); // Drive ViewContract from ViewContractObservable without WhenAny*/expression trees (AOT-trimmer friendly). // We always publish an initial null contract to preserve the original StartWith(null) behavior. @@ -255,8 +255,8 @@ private void Initialize() if (view is not NSViewController viewController) { - // view?.GetType().FullName may be null when the runtime type name is unavailable; the message still identifies the expected type. - throw new InvalidOperationException($"Resolved view type '{view?.GetType().FullName}' is not a '{typeof(NSViewController).FullName}'."); + // view.GetType().FullName may be null when the runtime type name is unavailable; the message still identifies the expected type. + throw new InvalidOperationException($"Resolved view type '{view.GetType().FullName}' is not a '{typeof(NSViewController).FullName}'."); } view.ViewModel = x.ViewModel; @@ -277,7 +277,7 @@ private void Initialize() /// The new contract value. private void SetViewContract(string? contract) { - this.RaiseAndSetIfChanged(ref _viewContract, contract, nameof(ViewContract)); + _ = this.RaiseAndSetIfChanged(ref _viewContract, contract, nameof(ViewContract)); } /// diff --git a/src/ReactiveUI/Platforms/ios/IosLinkerOverrides.cs b/src/ReactiveUI/Platforms/ios/IosLinkerOverrides.cs new file mode 100644 index 0000000000..f6514b15ef --- /dev/null +++ b/src/ReactiveUI/Platforms/ios/IosLinkerOverrides.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +using UIKit; + +#if REACTIVE_SHIM +namespace ReactiveUI.Reactive.Cocoa; +#else +namespace ReactiveUI.Cocoa; +#endif + +/// Forces the linker to preserve iOS-only UIKit members not present on every Apple platform. +[Preserve(AllMembers = true)] +[SuppressMessage("Minor Code Smell", "S1481:Unused local variables should be removed", Justification = "Locals force the linker to preserve these members.")] +internal class IosLinkerOverrides +{ + /// Forces the linker to preserve iOS-only UIKit members (UISlider, UIRefreshControl, UISwitch). + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used by linker.")] + public void KeepMe() + { + // UISlider + var slider = new UISlider { Value = default }; + _ = slider.Value; + + // UIRefreshControl + var rc = new UIRefreshControl(); + rc.ValueChanged += LinkerOverrides.Eh; + rc.ValueChanged -= LinkerOverrides.Eh; + + // UISwitch + var sw = new UISwitch(); + sw.ValueChanged += LinkerOverrides.Eh; + sw.On = true; + } +} diff --git a/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs b/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs index 9353fd79d7..645e9535e1 100644 --- a/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs +++ b/src/ReactiveUI/Platforms/mac/AutoSuspendHelper.cs @@ -105,7 +105,7 @@ public NSApplicationTerminateReply ApplicationShouldTerminate(NSApplication send ThrowIfDisposed(); // Ensure the persist notification is emitted on the main thread, as callers typically interact with AppKit. - RxSchedulers.MainThreadScheduler.Schedule(() => + _ = RxSchedulers.MainThreadScheduler.Schedule(() => _shouldPersistState.OnNext( Scope.Create(() => sender.ReplyToApplicationShouldTerminate(true)))); @@ -207,7 +207,7 @@ private static void EnsureMethodsNotOverloadedCached() } Reflection.ThrowIfMethodsNotOverloaded( - nameof(AutoSuspendHelper), + nameof(AutoSuspendHelper<>), type, nameof(ApplicationShouldTerminate), nameof(DidFinishLaunching), diff --git a/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs b/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs index e011df21f5..ce8793a830 100644 --- a/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs +++ b/src/ReactiveUI/Platforms/mac/ReactiveWindowController.cs @@ -133,7 +133,7 @@ public override void WindowDidLoad() // subscribe to listen to window closing // notification to support (de)activation - NSNotificationCenter + _ = NSNotificationCenter .DefaultCenter .AddObserver(NSWindow.WillCloseNotification, _ => _deactivated.OnNext(RxVoid.Default), Window); diff --git a/src/ReactiveUI/Platforms/tvos/LinkerOverrides.cs b/src/ReactiveUI/Platforms/tvos/LinkerOverrides.cs deleted file mode 100644 index 84eb086565..0000000000 --- a/src/ReactiveUI/Platforms/tvos/LinkerOverrides.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; -using UIKit; - -#if REACTIVE_SHIM -namespace ReactiveUI.Reactive.Cocoa; -#else -namespace ReactiveUI.Cocoa; -#endif -/// This class exists to force the MT linker to include properties called by RxUI via reflection. -[Preserve(AllMembers = true)] -[SuppressMessage("Major Code Smell", "S1656:Useless self-assignment", Justification = "Self-assignments force the linker to preserve these members.")] -[SuppressMessage("Minor Code Smell", "S1481:Unused local variables should be removed", Justification = "Self-assignments force the linker to preserve these members.")] -internal class LinkerOverrides -{ - /// Forces the linker to preserve UIKit members accessed by ReactiveUI via reflection. - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Needed for linking.")] - [SuppressMessage("Major Bug", "SST1189:Remove this self-assignment", Justification = "Deliberate self-assignment preserves both the getter and setter from the linker.")] - public void KeepMe() - { - // UIButton - var btn = new UIButton(); - var title = btn.Title(UIControlState.Disabled); - btn.SetTitle("foo", UIControlState.Disabled); - btn.TitleLabel.Text = btn.TitleLabel.Text; - - // UITextView - var tv = new UITextView(); - tv.Text = tv.Text; - - // UITextField - var tf = new UITextField(); - tv.Text = tf.Text; - - // UIImageView - var iv = new UIImageView(); - iv.Image = iv.Image; - - // UI Label - var lbl = new UILabel(); - lbl.Text = lbl.Text; - - // UI Control - var ctl = new UIControl(); - ctl.Enabled = ctl.Enabled; - ctl.Selected = ctl.Selected; - - static void Eh(object? s, EventArgs e) - { - // Intentionally empty: the handler exists only to force the linker to preserve - // the event add/remove accessors; no runtime behavior is needed. - } - - ctl.TouchUpInside += Eh; - ctl.TouchUpInside -= Eh; - - // UIBarButtonItem - var bbi = new UIBarButtonItem(); - bbi.Clicked += Eh; - bbi.Clicked -= Eh; - - Eh(null, EventArgs.Empty); - } -} diff --git a/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs b/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs index 0210003040..39d711ebb0 100644 --- a/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs +++ b/src/ReactiveUI/Platforms/tvos/UIKitCommandBinders.cs @@ -52,18 +52,15 @@ public UIKitCommandBinders() affinity: 10, static (cmd, t, cp) => { - if (t is not UIBarButtonItem item) - { - return Scope.Empty; - } - - return ForEvent( - command: cmd, - target: item, - commandParameter: cp, - addHandler: h => item.Clicked += h, - removeHandler: h => item.Clicked -= h, - enabledProperty: UIBarButtonItemEnabledProperty); + return t is not UIBarButtonItem item + ? Scope.Empty + : ForEvent( + command: cmd, + target: item, + commandParameter: cp, + addHandler: h => item.Clicked += h, + removeHandler: h => item.Clicked -= h, + enabledProperty: UIBarButtonItemEnabledProperty); }); } diff --git a/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs b/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs index 86378b6c4c..1c6e3e6e96 100644 --- a/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs +++ b/src/ReactiveUI/Platforms/uikit-common/CommonReactiveSource.cs @@ -73,7 +73,7 @@ public CommonReactiveSource(IUICollViewAdapter adapter) _mainThreadId = Environment.CurrentManagedThreadId; _mainDisposables = []; - _sectionInfoDisposable = new SwapDisposable(); + _sectionInfoDisposable = new(); _mainDisposables.Add(_sectionInfoDisposable); _pendingChanges = []; @@ -162,7 +162,7 @@ public TUIViewCell GetCell(NSIndexPath indexPath) var section = _sectionInfo[indexPath.Section]; var viewModel = ((IList)section.Collection!)[indexPath.Row]; - var key = section?.CellKeySelector?.Invoke(viewModel) ?? NSString.Empty; + var key = section.CellKeySelector?.Invoke(viewModel) ?? NSString.Empty; var cell = _adapter.DequeueReusableCell(key, indexPath); if (cell is IViewFor view) @@ -171,7 +171,7 @@ public TUIViewCell GetCell(NSIndexPath indexPath) view.ViewModel = viewModel; } - var initializeCellAction = section?.InitializeCellAction ?? NoOpInitializeCell; + var initializeCellAction = section.InitializeCellAction ?? NoOpInitializeCell; initializeCellAction(cell); return cell; @@ -196,13 +196,11 @@ private static void NoOpInitializeCell(TUIViewCell cell) /// The pending change. /// An enumerable of updates. /// Thrown when the action is not supported. - private static IEnumerable GetUpdatesForEvent(PendingChange pendingChange) => + private static List GetUpdatesForEvent(PendingChange pendingChange) => pendingChange.Action switch { NotifyCollectionChangedAction.Add => - Enumerable - .Range(pendingChange.NewStartingIndex, pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count) - .Select(Update.CreateAdd), + GetAddUpdates(pendingChange), NotifyCollectionChangedAction.Remove => GetRemoveUpdates(pendingChange), @@ -221,30 +219,70 @@ private static IEnumerable GetUpdatesForEvent(PendingChange pendingChang /// Builds the delete updates for a change. /// The pending change. /// An enumerable of delete updates. - private static IEnumerable GetRemoveUpdates(PendingChange pendingChange) => - Enumerable - .Range(pendingChange.OldStartingIndex, pendingChange.OldItems is null ? 1 : pendingChange.OldItems.Count) - .Select(_ => Update.CreateDelete(pendingChange.OldStartingIndex)); + private static List GetRemoveUpdates(PendingChange pendingChange) + { + var count = pendingChange.OldItems is null ? 1 : pendingChange.OldItems.Count; + List updates = new(count); + for (var i = 0; i < count; i++) + { + updates.Add(Update.CreateDelete(pendingChange.OldStartingIndex)); + } + + return updates; + } + + /// Builds the add updates for a change. + /// The pending change. + /// An enumerable of add updates. + private static List GetAddUpdates(PendingChange pendingChange) + { + var count = pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count; + List updates = new(count); + for (var i = 0; i < count; i++) + { + updates.Add(Update.CreateAdd(pendingChange.NewStartingIndex + i)); + } + + return updates; + } /// Builds the delete-then-add updates for a change. /// The pending change. /// An enumerable of delete and add updates. - private static IEnumerable GetMoveUpdates(PendingChange pendingChange) => - Enumerable - .Range(pendingChange.OldStartingIndex, pendingChange.OldItems is null ? 1 : pendingChange.OldItems.Count) - .Select(Update.CreateDelete) - .Concat( - Enumerable - .Range(pendingChange.NewStartingIndex, pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count) - .Select(Update.CreateAdd)); + private static List GetMoveUpdates(PendingChange pendingChange) + { + var oldCount = pendingChange.OldItems is null ? 1 : pendingChange.OldItems.Count; + var newCount = pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count; + List updates = new(oldCount + newCount); + for (var i = 0; i < oldCount; i++) + { + updates.Add(Update.CreateDelete(pendingChange.OldStartingIndex + i)); + } + + for (var i = 0; i < newCount; i++) + { + updates.Add(Update.CreateAdd(pendingChange.NewStartingIndex + i)); + } + + return updates; + } /// Builds the delete-then-add updates for a change. /// The pending change. /// An enumerable of paired delete and add updates. - private static IEnumerable GetReplaceUpdates(PendingChange pendingChange) => - Enumerable - .Range(pendingChange.NewStartingIndex, pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count) - .SelectMany(static x => new[] { Update.CreateDelete(x), Update.CreateAdd(x) }); + private static List GetReplaceUpdates(PendingChange pendingChange) + { + var count = pendingChange.NewItems is null ? 1 : pendingChange.NewItems.Count; + List updates = new(count * 2); + for (var i = 0; i < count; i++) + { + var index = pendingChange.NewStartingIndex + i; + updates.Add(Update.CreateDelete(index)); + updates.Add(Update.CreateAdd(index)); + } + + return updates; + } /// Called before changes. Disposes subscriptions for the current SectionInfo. private void SectionInfoChanging() diff --git a/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs b/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs index ae9b50c373..3bbc56f02a 100644 --- a/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs +++ b/src/ReactiveUI/Platforms/uikit-common/FlexibleCommandBinder.cs @@ -510,7 +510,7 @@ protected void Register(Type type, int affinity, FuncThis class exists to force the MT linker to include properties called by RxUI via reflection. [Preserve(AllMembers = true)] [SuppressMessage("Major Code Smell", "S1656:Useless self-assignment", Justification = "Self-assignments force the linker to preserve these members.")] @@ -28,55 +29,49 @@ public void KeepMe() btn.SetTitle("foo", UIControlState.Disabled); btn.TitleLabel.Text = btn.TitleLabel.Text; - // UISlider - var slider = new UISlider(); - slider.Value = slider.Value; // Get and set - + // Each control below references both the setter and the getter of the property so the + // trimmer/linker preserves both accessors (these members are resolved at runtime). The + // initializer (`{ Prop = default }`) keeps the setter; the discard read (`_ = x.Prop`) + // keeps the getter. A self-assign (`x.Prop = x.Prop`) would do the same but can't be + // expressed as an initializer, so it is split here to keep the get + set intent explicit. // UITextView - var tv = new UITextView(); - tv.Text = tv.Text; + var tv = new UITextView { Text = default }; + _ = tv.Text; // UITextField - var tf = new UITextField(); - tf.Text = tf.Text; + var tf = new UITextField { Text = default }; + _ = tf.Text; // UIImageView - var iv = new UIImageView(); - iv.Image = iv.Image; + var iv = new UIImageView { Image = default }; + _ = iv.Image; // UI Label - var lbl = new UILabel(); - lbl.Text = lbl.Text; + var lbl = new UILabel { Text = default }; + _ = lbl.Text; // UI Control - var ctl = new UIControl(); - ctl.Enabled = ctl.Enabled; - ctl.Selected = ctl.Selected; - - static void Eh(object? s, EventArgs e) - { - // Intentionally empty: the handler exists only to force the linker to preserve - // the event add/remove accessors; no runtime behavior is needed. - } + var ctl = new UIControl { Enabled = default, Selected = default }; + _ = ctl.Enabled; + _ = ctl.Selected; ctl.TouchUpInside += Eh; ctl.TouchUpInside -= Eh; - // UIRefreshControl - var rc = new UIRefreshControl(); - rc.ValueChanged += Eh; - rc.ValueChanged -= Eh; - // UIBarButtonItem var bbi = new UIBarButtonItem(); bbi.Clicked += Eh; bbi.Clicked -= Eh; - // UISwitch - var sw = new UISwitch(); - sw.ValueChanged += Eh; - sw.On = true; - Eh(null, EventArgs.Empty); } + + /// Empty handler whose only purpose is to force the linker to preserve event add/remove accessors. + /// The event sender; unused. + /// The event arguments; unused. + internal static void Eh(object? s, EventArgs e) + { + // Intentionally empty: the handler exists only to force the linker to preserve + // the event add/remove accessors; no runtime behavior is needed. + } } diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs index 248134014e..445acd4ba3 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveCollectionViewSource.cs @@ -52,7 +52,7 @@ public ReactiveCollectionViewSource(UICollectionView collectionView, INotifyColl public ReactiveCollectionViewSource(UICollectionView collectionView) { var adapter = new UICollectionViewAdapter(collectionView); - _commonSource = new CommonReactiveSource>(adapter); + _commonSource = new(adapter); } /// diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs index 5af58b7de3..3630774641 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSource.cs @@ -56,8 +56,8 @@ public ReactiveTableViewSource(UITableView tableView, INotifyCollectionChanged c /// The table view. public ReactiveTableViewSource(UITableView tableView) { - _adapter = new UITableViewAdapter(tableView); - _commonSource = new CommonReactiveSource>(_adapter); + _adapter = new(tableView); + _commonSource = new(_adapter); } /// @@ -155,12 +155,9 @@ public override nint RowsInSection(UITableView tableView, nint section) { // iOS may call this method even when we have no sections, but only if we've overriden // EstimatedHeight(UITableView, NSIndexPath) in our UITableViewSource - if (section >= _commonSource.NumberOfSections()) - { - return 0; - } - - return _commonSource.RowsInSection((int)section); + return section >= _commonSource.NumberOfSections() + ? 0 + : _commonSource.RowsInSection((int)section); } /// diff --git a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs index 1f822d35d5..92736002df 100644 --- a/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs +++ b/src/ReactiveUI/Platforms/uikit-common/ReactiveTableViewSourceExtensions.cs @@ -221,7 +221,7 @@ public IDisposable BindTo( var source = new ReactiveTableViewSource(tableView); if (initSource is not null) { - initSource(source); + _ = initSource(source); } var bind = sectionsObservable.BindTo(source, static x => x.Data); diff --git a/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs b/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs index ac11727d10..4785add64b 100644 --- a/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs +++ b/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs @@ -68,7 +68,7 @@ public class RoutedViewHost : ReactiveNavigationController public RoutedViewHost() { ViewContractObservable = Signal.Emit(null); - _titleUpdater = new SwapDisposable(); + _titleUpdater = new(); _ = this.WhenActivated(d => { @@ -183,11 +183,7 @@ private IDisposable SubscribeToInitialStack() => foreach (var viewModel in x.NavigationStack) { - view = ResolveView(Router.GetCurrentViewModel(), null); - if (view is null) - { - throw new InvalidOperationException(nameof(view)); - } + view = ResolveView(Router.GetCurrentViewModel(), null) ?? throw new InvalidOperationException(nameof(view)); PushViewController(view, false); } @@ -248,7 +244,7 @@ private IDisposable SubscribeToStackReset( } _routerInstigated = true; - PopToRootViewController(true); + _ = PopToRootViewController(true); _routerInstigated = false; })); @@ -256,10 +252,11 @@ private IDisposable SubscribeToStackReset( /// A disposable that represents the subscription. private IDisposable SubscribeToNavigateBack() => this.WhenAnyObservable(x => x.Router!.NavigateBack!) - .Subscribe(new DelegateObserver(_ => + .Subscribe(new DelegateObserver(navigateBack => { + _ = navigateBack; _routerInstigated = true; - PopViewController(true); + _ = PopViewController(true); _routerInstigated = false; })); diff --git a/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs b/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs index 68d96b6912..6e5fbcd30d 100644 --- a/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs +++ b/src/ReactiveUI/Platforms/uikit-common/UICollectionViewAdapter.cs @@ -34,7 +34,7 @@ internal class UICollectionViewAdapter : IUICollViewAdapter(false); + _isReloadingData = new(false); } /// @@ -54,7 +54,7 @@ public void ReloadData() // since ReloadData() queues the appropriate messages on the UI thread, we know we're done reloading // when this subsequent message is processed (with one caveat - see FinishReloadData for details) - RxSchedulers.MainThreadScheduler.Schedule(FinishReloadData); + _ = RxSchedulers.MainThreadScheduler.Schedule(FinishReloadData); } /// UICollectionView no longer has these methods so these are no-ops. diff --git a/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs b/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs index 0929b8425e..ba23c8e252 100644 --- a/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs +++ b/src/ReactiveUI/Platforms/uikit-common/UITableViewAdapter.cs @@ -32,7 +32,7 @@ internal class UITableViewAdapter : IUICollViewAdapter(false); + _isReloadingData = new(false); } /// @@ -70,7 +70,7 @@ public void ReloadData() // since ReloadData() queues the appropriate messages on the UI thread, we know we're done reloading // when this subsequent message is processed (with one caveat - see FinishReloadData for details) - RxSchedulers.MainThreadScheduler.Schedule(FinishReloadData); + _ = RxSchedulers.MainThreadScheduler.Schedule(FinishReloadData); } /// diff --git a/src/Shared/ArgumentValidation.cs b/src/Shared/ArgumentValidation.cs index 01ebdc61a2..b52d587bc4 100644 --- a/src/Shared/ArgumentValidation.cs +++ b/src/Shared/ArgumentValidation.cs @@ -313,7 +313,7 @@ public static void ThrowIfNotOfType( return; } - throw new ArgumentException($"Argument must be of type {typeof(T).Name}.", paramName); + throw new ArgumentException($"Argument must be of type {nameof(T)}.", paramName); } /// Throws an if is default. diff --git a/src/Shared/Platform/ChangeSetBinder.cs b/src/Shared/Platform/ChangeSetBinder.cs index f459a48242..3f59f8335a 100644 --- a/src/Shared/Platform/ChangeSetBinder.cs +++ b/src/Shared/Platform/ChangeSetBinder.cs @@ -133,6 +133,6 @@ private void RemoveAt(int index, T item) return; } - _items.Remove(item); + _ = _items.Remove(item); } } diff --git a/src/benchmarks/ReactiveUI.Benchmarks/NestedWhenAnyValueBenchmarks.cs b/src/benchmarks/ReactiveUI.Benchmarks/NestedWhenAnyValueBenchmarks.cs index e7c2ce5b93..4e575c5823 100644 --- a/src/benchmarks/ReactiveUI.Benchmarks/NestedWhenAnyValueBenchmarks.cs +++ b/src/benchmarks/ReactiveUI.Benchmarks/NestedWhenAnyValueBenchmarks.cs @@ -31,7 +31,7 @@ public class NestedWhenAnyValueBenchmarks [GlobalSetup] public void Setup() { - _viewModel = new NestedBenchmarkViewModel { Child = new BenchmarkViewModel() }; + _viewModel = new NestedBenchmarkViewModel { Child = new() }; _subscription = _viewModel.WhenAnyValue(x => x.Child!.First).Subscribe(_sink); } @@ -55,7 +55,7 @@ public void SwapParent() { for (var i = 0; i < EmissionCount; i++) { - _viewModel.Child = new BenchmarkViewModel(); + _viewModel.Child = new(); } } } diff --git a/src/benchmarks/ReactiveUI.Benchmarks/ObservableBenchmarkViewModel.cs b/src/benchmarks/ReactiveUI.Benchmarks/ObservableBenchmarkViewModel.cs index 570db927d5..7437ea1167 100644 --- a/src/benchmarks/ReactiveUI.Benchmarks/ObservableBenchmarkViewModel.cs +++ b/src/benchmarks/ReactiveUI.Benchmarks/ObservableBenchmarkViewModel.cs @@ -3,13 +3,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using ReactiveUI.Primitives.Signals; + namespace ReactiveUI.Benchmarks; /// A reactive object exposing a stable inner observable, used to drive the WhenAnyObservable benchmarks. internal sealed class ObservableBenchmarkViewModel : ReactiveObject, IDisposable { /// The inner observable values are pushed through. - private readonly ReactiveUI.Primitives.Signals.Signal _subject = new(); + private readonly Signal _subject = new(); /// Gets the observable that WhenAnyObservable subscribes to. public IObservable Values => _subject; diff --git a/src/examples/ReactiveUI.Builder.BlazorServer/Program.cs b/src/examples/ReactiveUI.Builder.BlazorServer/Program.cs index 574cea51e7..514d672f23 100644 --- a/src/examples/ReactiveUI.Builder.BlazorServer/Program.cs +++ b/src/examples/ReactiveUI.Builder.BlazorServer/Program.cs @@ -13,20 +13,20 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container. -builder.Services.AddRazorComponents() +_ = builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); -builder.Services.AddHostedService(); +_ = builder.Services.AddHostedService(); // Per-circuit (per tab) screen/bootstrapper: -builder.Services.AddScoped(); +_ = builder.Services.AddScoped(); -builder.Services.AddSingleton(); +_ = builder.Services.AddSingleton(); // This line connects Splat and standard Microsoft DI together builder.Services.UseMicrosoftDependencyResolver(); -RxAppBuilder.CreateReactiveUIBuilder() +_ = RxAppBuilder.CreateReactiveUIBuilder() .WithBlazor() .WithMessageBus() .WithViewsFromAssembly(typeof(Program).Assembly) @@ -37,21 +37,21 @@ // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { - app.UseExceptionHandler("/Error", true); + _ = app.UseExceptionHandler("/Error", true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); + _ = app.UseHsts(); } -app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); +_ = app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); -app.UseHttpsRedirection(); +_ = app.UseHttpsRedirection(); -app.UseAntiforgery(); +_ = app.UseAntiforgery(); -app.MapStaticAssets(); +_ = app.MapStaticAssets(); -app.MapRazorComponents() +_ = app.MapRazorComponents() .AddInteractiveServerRenderMode(); await app.RunAsync(); diff --git a/src/examples/ReactiveUI.Builder.BlazorServer/Services/ReactiveUiAppHostedService.cs b/src/examples/ReactiveUI.Builder.BlazorServer/Services/ReactiveUiAppHostedService.cs index 5ac61523d3..00a00cd6ef 100644 --- a/src/examples/ReactiveUI.Builder.BlazorServer/Services/ReactiveUiAppHostedService.cs +++ b/src/examples/ReactiveUI.Builder.BlazorServer/Services/ReactiveUiAppHostedService.cs @@ -33,7 +33,7 @@ public Task StartAsync(CancellationToken cancellationToken) Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ReactiveUI.Builder.BlazorServer", "state.json"); - Directory.CreateDirectory(Path.GetDirectoryName(statePath)!); + _ = Directory.CreateDirectory(Path.GetDirectoryName(statePath)!); _driver = new(statePath); diff --git a/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/AppBootstrapper.cs b/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/AppBootstrapper.cs index a909f59f24..f6b57380ed 100644 --- a/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/AppBootstrapper.cs +++ b/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/AppBootstrapper.cs @@ -17,7 +17,7 @@ public AppBootstrapper() Router = new(); - Router.Navigate.Execute(new LobbyViewModel(this)).Subscribe(Witness.Create(static _ => { })); + _ = Router.Navigate.Execute(new LobbyViewModel(this)).Subscribe(Witness.Create(static _ => { })); } /// Gets the unique identifier for the Blazor circuit tab associated with this instance. diff --git a/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/ChatRoomViewModel.cs b/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/ChatRoomViewModel.cs index 9c798e3c07..24902d071b 100644 --- a/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/ChatRoomViewModel.cs +++ b/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/ChatRoomViewModel.cs @@ -52,7 +52,7 @@ public ChatRoomViewModel(IScreen hostScreen, ChatRoom room, string user) _canSendPropertyHelper = canSend.ToProperty(this, nameof(SendMessage)); // Observe new incoming messages via MessageBus using the room name as the contract across instances - MessageBus.Current.Listen(room.Name) + _ = MessageBus.Current.Listen(room.Name) .EmitIfQuiet(TimeSpan.FromMilliseconds(33)) .Where(x => x.InstanceId != _senderInstanceId) .Subscribe(Witness.Create(msg => diff --git a/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/LobbyViewModel.cs b/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/LobbyViewModel.cs index af846c8f8f..78ce3754df 100644 --- a/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/LobbyViewModel.cs +++ b/src/examples/ReactiveUI.Builder.BlazorServer/ViewModels/LobbyViewModel.cs @@ -89,13 +89,13 @@ public LobbyViewModel(IScreen hostScreen) .Concat(Signal.Blend(localRoomsChanged, remoteRoomsChanged) .EmitIfQuiet(TimeSpan.FromMilliseconds(50), RxSchedulers.TaskpoolScheduler)); - this.WhenAnyObservable(x => x.RoomsChanged) + _ = this.WhenAnyObservable(x => x.RoomsChanged) .Select(_ => (IReadOnlyList)[.. GetState().Rooms]) .ObserveOn(RxSchedulers.MainThreadScheduler) .ToProperty(this, nameof(Rooms), out _rooms); // Request a snapshot from peers shortly after activation - RxSchedulers.MainThreadScheduler.Schedule(RxVoid.Default, TimeSpan.FromMilliseconds(500), (_, _) => + _ = RxSchedulers.MainThreadScheduler.Schedule(RxVoid.Default, TimeSpan.FromMilliseconds(500), (_, _) => { var req = new RoomEventMessage(Services.RoomEventKind.SyncRequest, string.Empty) { @@ -211,7 +211,7 @@ private static void ApplyRoomEvent(RoomEventMessage evt) case Services.RoomEventKind.Remove: { - state.Rooms.RemoveAll(r => string.Equals(r.Name, evt.RoomName, StringComparison.OrdinalIgnoreCase)); + _ = state.Rooms.RemoveAll(r => string.Equals(r.Name, evt.RoomName, StringComparison.OrdinalIgnoreCase)); break; } } @@ -222,7 +222,16 @@ private void CreateRoomImpl() { var name = RoomName.Trim(); var state = GetState(); - var existing = state.Rooms.FirstOrDefault(r => string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase)); + ChatRoom? existing = null; + foreach (var room in state.Rooms) + { + if (string.Equals(room.Name, name, StringComparison.OrdinalIgnoreCase)) + { + existing = room; + break; + } + } + if (existing is null) { var room = new ChatRoom { Name = name }; diff --git a/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs b/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs index 54cd88ed7b..dc8544564e 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/App.xaml.cs @@ -4,147 +4,86 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Windows; using ReactiveUI.Builder.WpfApp.Models; +using ReactiveUI.Builder.WpfApp.Services; +using ReactiveUI.Builder.WpfApp.ViewModels; using Splat; namespace ReactiveUI.Builder.WpfApp; /// Interaction logic for App.xaml. -[SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "Disposed on application exit in OnExit")] public partial class App : Application { - /// The small object cache size configured for ReactiveUI. + /// The small object-cache size configured for ReactiveUI. private const int SmallCacheSize = 100; - /// The large object cache size configured for ReactiveUI. + /// The large object-cache size configured for ReactiveUI. private const int LargeCacheSize = 400; - /// The suspension driver that persists and restores the chat state to a JSON file on disk. - private Services.FileJsonSuspensionDriver? _driver; + /// The suspension driver that persists and restores the transaction journal. + private FileJsonSuspensionDriver? _driver; - /// The service that relays chat messages and room events between running app instances. - private Services.ChatNetworkService? _networkService; - - /// The coordinator that tracks how many app instances are running so the last one can save state. - private Services.AppLifetimeCoordinator? _lifetime; - - /// Raises the event. - /// A that contains the event data. + /// Raises the event. + /// A that contains the event data. protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); - // Initialize ReactiveUI via the Builder only - RxAppBuilder.CreateReactiveUIBuilder() + // The whole stack is configured through the builder: the WPF platform, automatic view discovery, + // a typed suspension host, cache sizes, an exception handler, the message bus, and the services. + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithWpf() - .WithViewsFromAssembly(typeof(App).Assembly) // auto-register all IViewFor in this assembly - - ////.RegisterView() - ////.RegisterView() - ////.RegisterView() - .WithSuspensionHost() // Configure typed suspension host - .WithCacheSizes(SmallCacheSize, LargeCacheSize) // Customize cache sizes + .WithViewsFromAssembly(typeof(App).Assembly) + .WithSuspensionHost() + .WithCacheSizes(SmallCacheSize, LargeCacheSize) .WithExceptionHandler(new LoggingExceptionObserver()) .WithMessageBus() .WithRegistration(static r => { - // Register IScreen as a singleton so all resolutions share the same Router - r.RegisterLazySingleton(static () => new ViewModels.AppBootstrapper()); - - // Cross-process instance lifetime coordination - r.RegisterLazySingleton(static () => new Services.AppLifetimeCoordinator()); - - // Network service used to broadcast/receive messages across instances - r.RegisterLazySingleton(static () => new Services.ChatNetworkService()); + r.RegisterLazySingleton(static () => new MockPaymentProcessor()); + r.RegisterLazySingleton(static () => new AppBootstrapper()); }) .Build(); - // Setup Suspension - RxSuspension.SuspensionHost.CreateNewAppState = static () => new ChatState(); + RxSuspension.SuspensionHost.CreateNewAppState = static () => new TerminalState(); var statePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ReactiveUI.Builder.WpfApp", - "state.json"); - Directory.CreateDirectory(Path.GetDirectoryName(statePath)!); - + "journal.json"); + _ = Directory.CreateDirectory(Path.GetDirectoryName(statePath)!); _driver = new(statePath); - // Set an initial state instantly to avoid blocking UI - RxSuspension.SuspensionHost.AppState = new ChatState(); - - // Load persisted state asynchronously and update UI when ready - _ = _driver - .LoadState() - .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe( - static stateObj => - { - RxSuspension.SuspensionHost.AppState = stateObj; - MessageBus.Current.SendMessage(new ChatStateChanged()); - Trace.TraceInformation("[App] State loaded"); - }, - static ex => Trace.TraceInformation($"[App] State load failed: {ex.Message}")); - - // Resolve coordinator + network service - _lifetime = Locator.Current.GetService(); - var count = _lifetime?.Increment() ?? 1; - Trace.TraceInformation($"[App] Instance started. Count={count} Id={Services.AppInstance.Id}"); - - _networkService = Locator.Current.GetService(); - _networkService?.Start(); // starts background receive loop, no UI blocking - - // Resolve AppBootstrapper once and use it for both ViewModel and Router - var appBoot = (ViewModels.AppBootstrapper)Locator.Current.GetService()!; - - // Create and show the shell - var mainWindow = new MainWindow { ViewModel = appBoot }; - - // Replace RoutedViewHost router to ensure it uses the same singleton instance - if (mainWindow.Content is RoutedViewHost host) - { - host.Router = appBoot.Router; - } + // Restore the persisted journal before the first navigation so the terminal and journal see it. + RxSuspension.SuspensionHost.AppState = + (_driver.LoadState().GetAwaiter().GetResult() as TerminalState) ?? new TerminalState(); - MainWindow = mainWindow; - mainWindow.Show(); + var window = new MainWindow(); + MainWindow = window; + window.Show(); } - /// Raises the event. - /// An that contains the event data. + /// Raises the event and persists the journal. + /// An that contains the event data. protected override void OnExit(ExitEventArgs e) { - try + if (_driver is not null && RxSuspension.SuspensionHost.AppState is TerminalState state) { - var remaining = _lifetime?.Decrement() ?? 0; - Trace.TraceInformation($"[App] Instance exiting. Remaining={remaining} Id={Services.AppInstance.Id}"); - - // Only the last instance persists the final state to the central store - if (remaining == 0 && _driver is not null && RxSuspension.SuspensionHost.AppState is not null) - { - _driver.SaveState(RxSuspension.SuspensionHost.AppState).GetAwaiter().GetResult(); - } - } - finally - { - _networkService?.Dispose(); - base.OnExit(e); + _ = _driver.SaveState(state).GetAwaiter().GetResult(); } + + base.OnExit(e); } - /// Logs unhandled ReactiveUI exceptions and breaks into the debugger when attached. + /// Logs unhandled ReactiveUI exceptions and breaks into the debugger when one is attached. private sealed class LoggingExceptionObserver : IObserver { /// public void OnNext(Exception value) { - Trace.TraceInformation($"[ReactiveUI] Unhandled exception: {value}"); + Trace.TraceError($"[ReactiveUI] Unhandled exception: {value}"); if (!Debugger.IsAttached) { return; diff --git a/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml b/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml index b76f2f9a31..ac1e408532 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml +++ b/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml @@ -4,7 +4,16 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Title="MainWindow" - Width="800" - Height="450" - mc:Ignorable="d" /> + Title="Mock EFTPOS Terminal" + Width="440" + Height="760" + ResizeMode="CanMinimize" + WindowStartupLocation="CenterScreen" + mc:Ignorable="d"> + + + + + + + diff --git a/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml.cs b/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml.cs index 7205d2aae2..d55df1cf9a 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/MainWindow.xaml.cs @@ -4,17 +4,20 @@ // See the LICENSE file in the project root for full license information. using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using ReactiveUI.Builder.WpfApp.ViewModels; using Splat; namespace ReactiveUI.Builder.WpfApp; -/// Interaction logic for MainWindow.xaml. -public partial class MainWindow : IViewFor +/// The application shell window; hosts the router through a . +public partial class MainWindow : IViewFor { - /// The view model property. + /// Identifies the dependency property. public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( nameof(ViewModel), - typeof(ViewModels.AppBootstrapper), + typeof(AppBootstrapper), typeof(MainWindow), new(null)); @@ -23,37 +26,32 @@ public MainWindow() { InitializeComponent(); - // Set up content host with routing + var screen = (AppBootstrapper)Locator.Current.GetService()!; + ViewModel = screen; Content = new RoutedViewHost { - Router = Locator.Current.GetService()!.Router, - DefaultContent = new System.Windows.Controls.TextBlock + Router = screen.Router, + DefaultContent = new TextBlock { - Text = "Loading...", + Text = "Loading…", + Foreground = Brushes.Gray, HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } + VerticalAlignment = VerticalAlignment.Center, + }, }; - ViewModel = (ViewModels.AppBootstrapper)Locator.Current.GetService()!; } - /// - /// Gets or sets the ViewModel corresponding to this specific View. This should be - /// a DependencyProperty if you're using XAML. - /// - public ViewModels.AppBootstrapper? ViewModel + /// Gets or sets the root screen that backs this shell. + public AppBootstrapper? ViewModel { - get => (ViewModels.AppBootstrapper?)GetValue(ViewModelProperty); + get => (AppBootstrapper?)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } - /// - /// Gets or sets the ViewModel corresponding to this specific View. This should be - /// a DependencyProperty if you're using XAML. - /// + /// object? IViewFor.ViewModel { get => ViewModel; - set => ViewModel = (ViewModels.AppBootstrapper?)value; + set => ViewModel = (AppBootstrapper?)value; } } diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatMessage.cs b/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatMessage.cs deleted file mode 100644 index 8e0ab46c65..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatMessage.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; - -namespace ReactiveUI.Builder.WpfApp.Models; - -/// A single chat message. -public class ChatMessage -{ - /// Gets or sets the sender name. - public string Sender { get; set; } = string.Empty; - - /// Gets or sets the message text. - public string Text { get; set; } = string.Empty; - - /// Gets or sets the timestamp. - [SuppressMessage("Major Code Smell", "S6354:Use a testable date/time provider", Justification = "Not available all TFMs")] - public DateTimeOffset Timestamp { get; set; } = -#if NET8_0_OR_GREATER - TimeProvider.System.GetUtcNow(); -#else - DateTimeOffset.Now; -#endif -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatNetworkMessage.cs b/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatNetworkMessage.cs deleted file mode 100644 index 90cef84f35..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatNetworkMessage.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -namespace ReactiveUI.Builder.WpfApp.Models; - -/// Network message payload used to broadcast chat messages. -public sealed class ChatNetworkMessage -{ - /// Initializes a new instance of the class. - public ChatNetworkMessage() - { - } - - /// Initializes a new instance of the class with values. - /// The unique identifier for the room. - /// The human-readable room name used as the MessageBus contract. - /// The sender name. - /// The message text. - /// The message timestamp. - public ChatNetworkMessage(string roomId, string roomName, string sender, string text, DateTimeOffset timestamp) - { - RoomId = roomId; - RoomName = roomName; - Sender = sender; - Text = text; - Timestamp = timestamp; - } - - /// Gets or sets the room ID. - public string RoomId { get; set; } = string.Empty; - - /// Gets or sets the room name. - public string RoomName { get; set; } = string.Empty; - - /// Gets or sets the sender. - public string Sender { get; set; } = string.Empty; - - /// Gets or sets the message text. - public string Text { get; set; } = string.Empty; - - /// Gets or sets the timestamp. - public DateTimeOffset Timestamp { get; set; } - - /// Gets or sets the originating app instance id. - public Guid InstanceId { get; set; } -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatRoom.cs b/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatRoom.cs deleted file mode 100644 index 5bcbacb6f9..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatRoom.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; - -namespace ReactiveUI.Builder.WpfApp.Models; - -/// Represents a chat room with messages and members. -public class ChatRoom -{ - /// Gets or sets the room id. - public string Id { get; set; } = Guid.NewGuid().ToString("N"); - - /// Gets or sets the room name. - public string Name { get; set; } = string.Empty; - - /// Gets or sets the messages in the room. - [SuppressMessage("Major Code Smell", "S4004:Collection properties should be read only", Justification = "Public setter required for System.Text.Json deserialization of persisted state.")] - public ObservableCollection Messages { get; set; } = []; - - /// Gets or sets the members in the room. - [SuppressMessage("Major Code Smell", "S4004:Collection properties should be read only", Justification = "Public setter required for System.Text.Json deserialization of persisted state.")] - public List Members { get; set; } = []; -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatStateChanged.cs b/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatStateChanged.cs deleted file mode 100644 index e309de161b..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatStateChanged.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; - -namespace ReactiveUI.Builder.WpfApp.Models; - -/// Notification that the chat state has changed and observers should refresh. -public sealed record ChatStateChanged -{ - /// Gets the moment, in UTC, at which the chat state change was signalled. - [SuppressMessage("Major Code Smell", "S6354:Use a testable date/time provider", Justification = "Not available all TFMs")] - public DateTimeOffset Timestamp { get; init; } = -#if NET8_0_OR_GREATER - TimeProvider.System.GetUtcNow(); -#else - DateTimeOffset.UtcNow; -#endif -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Models/RoomEventMessage.cs b/src/examples/ReactiveUI.Builder.WpfApp/Models/RoomEventMessage.cs deleted file mode 100644 index 869e375364..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Models/RoomEventMessage.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; -using ReactiveUI.Builder.WpfApp.Services; - -namespace ReactiveUI.Builder.WpfApp.Models; - -/// Network event describing a change in the rooms list. -public sealed class RoomEventMessage -{ - /// Initializes a new instance of the class. - public RoomEventMessage() - { - } - - /// Initializes a new instance of the class. - /// The event kind. - /// The room name. - public RoomEventMessage(RoomEventKind kind, string roomName) - { - Kind = kind; - RoomName = roomName; - } - - /// Gets or sets the event kind. - public RoomEventKind Kind { get; set; } - - /// Gets or sets the room name for this event. - public string RoomName { get; set; } = string.Empty; - - /// Gets or sets the originating instance id. - public Guid InstanceId { get; set; } - - /// Gets or sets the current snapshot of room names. Used in response to SyncRequest. - [SuppressMessage("Major Code Smell", "S4004:Collection properties should be read only", Justification = "Public setter required for System.Text.Json deserialization of persisted state.")] - public List? Snapshot { get; set; } -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatState.cs b/src/examples/ReactiveUI.Builder.WpfApp/Models/TerminalState.cs similarity index 63% rename from src/examples/ReactiveUI.Builder.WpfApp/Models/ChatState.cs rename to src/examples/ReactiveUI.Builder.WpfApp/Models/TerminalState.cs index 1615ea35a9..7e0e22a10b 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/Models/ChatState.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/Models/TerminalState.cs @@ -3,17 +3,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; namespace ReactiveUI.Builder.WpfApp.Models; -/// The persisted chat application state. -public class ChatState +/// The persisted terminal state: the journal of completed transactions. +public sealed class TerminalState { - /// Gets or sets the available rooms. + /// Gets or sets the journal of completed transactions, most recent first. [SuppressMessage("Major Code Smell", "S4004:Collection properties should be read only", Justification = "Public setter required for System.Text.Json deserialization of persisted state.")] - public List Rooms { get; set; } = []; - - /// Gets or sets the local user's display name. - public string? DisplayName { get; set; } + public ObservableCollection Journal { get; set; } = []; } diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Models/Transaction.cs b/src/examples/ReactiveUI.Builder.WpfApp/Models/Transaction.cs new file mode 100644 index 0000000000..5718289857 --- /dev/null +++ b/src/examples/ReactiveUI.Builder.WpfApp/Models/Transaction.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Builder.WpfApp.Models; + +/// A single (mock) EFTPOS transaction recorded in the terminal journal. +public sealed class Transaction +{ + /// Gets or sets the bank authorization reference. + public string Reference { get; set; } = string.Empty; + + /// Gets or sets the transaction amount. + public decimal Amount { get; set; } + + /// Gets or sets a value indicating whether the transaction was approved. + public bool Approved { get; set; } + + /// Gets or sets the outcome message (an approval code or a decline reason). + public string Outcome { get; set; } = string.Empty; + + /// Gets or sets the card scheme used (for example, VISA). + public string CardScheme { get; set; } = string.Empty; + + /// Gets or sets the masked last four digits of the card. + public string CardLast4 { get; set; } = string.Empty; + + /// Gets or sets the moment the transaction completed. + public DateTimeOffset Timestamp { get; set; } +} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/AppInstance.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/AppInstance.cs deleted file mode 100644 index ab9f7d03fb..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Services/AppInstance.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -namespace ReactiveUI.Builder.WpfApp.Services; - -/// Provides a stable identifier that is unique to the current running instance of the application. -/// -/// The sample uses this id to tell instances apart when several copies of the app are running at once, -/// for example to ignore network packets that an instance broadcast to itself. -/// -internal static class AppInstance -{ - /// The identifier generated once per process, used to distinguish this app instance from others. - public static readonly Guid Id = Guid.NewGuid(); -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/AppLifetimeCoordinator.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/AppLifetimeCoordinator.cs deleted file mode 100644 index eedeeae1df..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Services/AppLifetimeCoordinator.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics; -using System.IO.MemoryMappedFiles; - -namespace ReactiveUI.Builder.WpfApp.Services; - -/// Cross-process instance counter. Used to determine when the last instance closes. -public sealed class AppLifetimeCoordinator : IDisposable -{ - /// The name of the shared memory-mapped file that holds the running instance count. - private const string MapName = "ReactiveUI.Builder.WpfApp.InstanceCounter"; - - /// The name of the system-wide mutex used to serialize updates to the instance count. - private const string MutexName = "ReactiveUI.Builder.WpfApp.InstanceMutex"; - - /// The size, in bytes, of the shared counter (a single 32-bit integer). - private const int CounterByteSize = 4; - - /// The maximum time to wait when acquiring the mutex before giving up. - private static readonly TimeSpan LockTimeout = TimeSpan.FromMilliseconds(500); - - /// The shared memory-mapped file backing the cross-process instance counter. - private readonly MemoryMappedFile _mmf; - - /// The named mutex guarding read/modify/write access to the counter. - private readonly Mutex _mutex; - - /// Initializes a new instance of the class. - public AppLifetimeCoordinator() - { - _mutex = new(false, MutexName, out _); - - try - { - _mmf = MemoryMappedFile.CreateOrOpen(MapName, CounterByteSize); - } - catch - { - // Fallback: create a per-user mapping name if needed - _mmf = MemoryMappedFile.CreateOrOpen(MapName + "." + Environment.UserName, CounterByteSize); - } - } - - /// Increments the instance count. - /// The new count after incrementing. - public int Increment() => UpdateCount(static c => c + 1); - - /// Decrements the instance count. - /// The new count after decrementing (0 means last instance is closing). - public int Decrement() => UpdateCount(static c => Math.Max(0, c - 1)); - - /// - public void Dispose() - { - _mutex.Dispose(); - _mmf.Dispose(); - } - - /// - /// Atomically reads the current instance count, applies the supplied transformation, and writes it back - /// while holding the cross-process mutex. - /// - /// A function that maps the current count to the new count. - /// The updated instance count. - private int UpdateCount(Func updater) - { - var locked = false; - try - { - try - { - locked = _mutex.WaitOne(LockTimeout); - } - catch (AbandonedMutexException) - { - // Consider it acquired in abandoned state - locked = true; - } - - using var view = _mmf.CreateViewAccessor(0, 4, MemoryMappedFileAccess.ReadWrite); - view.Read(0, out int current); - var updated = updater(current); - view.Write(0, updated); - view.Flush(); - return updated; - } - finally - { - if (locked) - { - try - { - _mutex.ReleaseMutex(); - } - catch (ApplicationException ex) - { - // The mutex was not held by the current thread (e.g. acquired in an abandoned state). - Trace.TraceWarning($"[Lifetime] Failed to release mutex: {ex.Message}"); - } - catch (ObjectDisposedException ex) - { - // The coordinator is being disposed concurrently; nothing further to release. - Trace.TraceWarning($"[Lifetime] Mutex already disposed during release: {ex.Message}"); - } - } - } - } -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs deleted file mode 100644 index 538ff2fb21..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics; -using System.Net; -using System.Net.Sockets; -using System.Text.Json; -using ReactiveUI.Builder.WpfApp.Models; - -namespace ReactiveUI.Builder.WpfApp.Services; - -/// A simple UDP-based network relay to share chat messages and room events between app instances. -public sealed class ChatNetworkService : IDisposable -{ - /// The MessageBus contract used for room add/remove/sync events. - private const string RoomsContract = "__rooms__"; - - /// The UDP port that all instances send to and listen on. - private const int Port = 54_545; - - /// The IPv4 local multicast address used to reach other instances on the same machine/network. - private static readonly IPAddress MulticastAddress = IPAddress.Parse("239.255.0.1"); - - /// The UDP client used to send outgoing packets. - private readonly UdpClient _udp; // sender - - /// The multicast endpoint that outgoing packets are addressed to. - private readonly IPEndPoint _sendEndpoint; - - /// Signals the background receive loop to stop when the service is disposed. - private readonly CancellationTokenSource _cts = new(); - - /// Initializes a new instance of the class. - public ChatNetworkService() - { - _sendEndpoint = new(MulticastAddress, Port); - _udp = new(AddressFamily.InterNetwork); - - try - { - // Enable multicast loopback so we can also receive our messages (we filter locally using InstanceId) - _udp.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 1); - _udp.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastLoopback, true); - } - catch (SocketException ex) - { - // Multicast options are best-effort; continue without them if the platform rejects them. - Trace.TraceWarning($"[Net] Failed to configure multicast socket options: {ex.Message}"); - } - - // Outgoing chat messages (default contract) - only send messages originating from this instance - MessageBus.Current.Listen() - .Where(static m => m.InstanceId == AppInstance.Id) - .ObserveOn(RxSchedulers.TaskpoolScheduler) - .Subscribe(Send); - - // Outgoing room events - only send messages originating from this instance - MessageBus.Current.Listen(RoomsContract) - .Where(static m => m.InstanceId == AppInstance.Id) - .ObserveOn(RxSchedulers.TaskpoolScheduler) - .Subscribe(Send); - - Trace.TraceInformation("[Net] ChatNetworkService initialized."); - } - - /// Starts the background receive loop. - public void Start() - { - Trace.TraceInformation("[Net] Starting receive loop..."); - Task.Run(ReceiveLoop, _cts.Token); - } - - /// - public void Dispose() - { - _cts.Cancel(); - _udp.Dispose(); - _cts.Dispose(); - Trace.TraceInformation("[Net] Disposed."); - } - - /// Configures and binds the supplied listener to the multicast group used by the service. - /// The UDP client to configure and bind. - /// if the listener started successfully; otherwise . - private static bool TryStartListener(UdpClient listener) - { - try - { - // Allow multiple processes to bind the same UDP port - listener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - listener.ExclusiveAddressUse = false; - listener.Client.Bind(new IPEndPoint(IPAddress.Any, Port)); - - // Join multicast group on default interface - listener.JoinMulticastGroup(MulticastAddress); - Trace.TraceInformation($"[Net] Listening on {MulticastAddress}:{Port}"); - return true; - } - catch (Exception ex) - { - Trace.TraceInformation($"[Net] Failed to start listener: {ex.Message}"); - return false; - } - } - - /// Deserializes a received packet and routes it to the appropriate handler. - /// The raw bytes of the received packet. - private static void DispatchPacket(byte[] buffer) - { - using var doc = JsonDocument.Parse(buffer); - var root = doc.RootElement; - var isRoomEvent = root.TryGetProperty("Kind", out _) || root.TryGetProperty("Snapshot", out _); - - if (isRoomEvent) - { - HandleRoomEvent(buffer); - } - else - { - HandleChatMessage(buffer); - } - } - - /// Deserializes and republishes a room event, ignoring packets that originated from this instance. - /// The raw bytes of the received room-event packet. - private static void HandleRoomEvent(byte[] buffer) - { - var evt = JsonSerializer.Deserialize(buffer); - if (evt is null || evt.InstanceId == AppInstance.Id) - { - // null payload or our own looped-back packet - return; - } - - Trace.TraceInformation($"[Net] RX RoomEvent {evt.Kind} name='{evt.RoomName}' from={evt.InstanceId}"); - MessageBus.Current.SendMessage(evt, RoomsContract); - } - - /// Deserializes and republishes a chat message, ignoring packets that originated from this instance. - /// The raw bytes of the received chat packet. - private static void HandleChatMessage(byte[] buffer) - { - var chat = JsonSerializer.Deserialize(buffer); - if (chat is null || chat.InstanceId == AppInstance.Id) - { - // null payload or our own looped-back packet - return; - } - - Trace.TraceInformation( - $"[Net] RX Chat '{chat.Text}' in '{chat.RoomName}' from={chat.Sender}/{chat.InstanceId}"); - MessageBus.Current.SendMessage(chat, chat.RoomName); - } - - /// - /// Continuously receives UDP packets, deserializes them into chat or room messages, and republishes - /// them on the local until cancellation is requested. - /// - /// A task that completes when the receive loop ends. - private async Task ReceiveLoop() - { - using var listener = new UdpClient(AddressFamily.InterNetwork); - if (!TryStartListener(listener)) - { - return; - } - - while (!_cts.IsCancellationRequested) - { - try - { - var result = await listener.ReceiveAsync().ConfigureAwait(false); - DispatchPacket(result.Buffer); - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - Trace.TraceInformation($"[Net] RX error: {ex.Message}"); - } - } - } - - /// Serializes the supplied message to JSON and broadcasts it to the multicast endpoint. - /// The chat or room message to broadcast. - private void Send(object message) - { - try - { - var bytes = JsonSerializer.SerializeToUtf8Bytes(message, message.GetType()); - _udp.Send(bytes, bytes.Length, _sendEndpoint); - switch (message) - { - case ChatNetworkMessage c: - { - Trace.TraceInformation($"[Net] TX Chat '{c.Text}' in '{c.RoomName}' from={c.Sender}/{c.InstanceId}"); - break; - } - - case RoomEventMessage r: - { - Trace.TraceInformation($"[Net] TX RoomEvent {r.Kind} name='{r.RoomName}' from={r.InstanceId}"); - break; - } - } - } - catch (Exception ex) - { - Trace.TraceInformation($"[Net] TX error: {ex.Message}"); - } - } -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs index 11d9696553..7f847806c3 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs @@ -10,21 +10,17 @@ namespace ReactiveUI.Builder.WpfApp.Services; -/// A suspension driver that persists and restores application state as JSON in a file on disk. +/// A suspension driver that persists and restores the terminal state as indented JSON on disk. /// -/// -/// Initializes a new instance of the class. -/// -/// The path. +/// Initializes a new instance of the class. +/// The file path the state is persisted to. public sealed class FileJsonSuspensionDriver(string path) : ISuspensionDriver { - /// The serializer options used when writing state, configured to produce indented (human-readable) JSON. + /// The serializer options used when writing state, configured to produce human-readable JSON. private readonly JsonSerializerOptions _options = new() { WriteIndented = true }; - /// Invalidates the application state (i.e. deletes it from disk). - /// - /// A completed observable. - /// + /// Invalidates the application state by deleting it from disk. + /// A completed observable. public IObservable InvalidateState() => Signal.Start( () => { @@ -38,23 +34,21 @@ public IObservable InvalidateState() => Signal.Start( RxSchedulers.TaskpoolScheduler); /// Loads the application state from persistent storage. - /// - /// An object observable. - /// + /// An observable that produces the loaded state. public IObservable LoadState() => Signal.Start( - () => + object? () => { if (!File.Exists(path)) { - return new(); + return new TerminalState(); } var json = File.ReadAllText(path); - return JsonSerializer.Deserialize(json) ?? new ChatState(); + return JsonSerializer.Deserialize(json) ?? new TerminalState(); }, RxSchedulers.TaskpoolScheduler); - /// Loads the application state from persistent storage using source-generated JSON metadata. + /// Loads the application state using source-generated JSON metadata. /// The type of state to load. /// The source-generated JSON type info. /// An observable that produces the deserialized state. @@ -74,15 +68,9 @@ public IObservable InvalidateState() => Signal.Start( /// Saves the application state to disk. /// The type of state to save. /// The application state. - /// - /// A completed observable. - /// + /// A completed observable. public IObservable SaveState(T state) => Signal.Start( - () => - { - var json = JsonSerializer.Serialize(state, _options); - File.WriteAllText(path, json); - }, + () => File.WriteAllText(path, JsonSerializer.Serialize(state, _options)), RxSchedulers.TaskpoolScheduler); /// Saves the application state to disk using source-generated JSON metadata. @@ -91,10 +79,6 @@ public IObservable SaveState(T state) => Signal.Start( /// The source-generated JSON type info. /// A completed observable. public IObservable SaveState(T state, JsonTypeInfo typeInfo) => Signal.Start( - () => - { - var json = JsonSerializer.Serialize(state, typeInfo); - File.WriteAllText(path, json); - }, + () => File.WriteAllText(path, JsonSerializer.Serialize(state, typeInfo)), RxSchedulers.TaskpoolScheduler); } diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/IPaymentProcessor.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/IPaymentProcessor.cs new file mode 100644 index 0000000000..16bca9ff22 --- /dev/null +++ b/src/examples/ReactiveUI.Builder.WpfApp/Services/IPaymentProcessor.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Builder.WpfApp.Models; + +namespace ReactiveUI.Builder.WpfApp.Services; + +/// Authorizes card payments for the terminal. +public interface IPaymentProcessor +{ + /// Authorizes a payment for the supplied amount. + /// The amount to authorize. + /// A token used to cancel the authorization. + /// The completed . + Task AuthorizeAsync(decimal amount, CancellationToken cancellationToken); +} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/MockPaymentProcessor.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/MockPaymentProcessor.cs new file mode 100644 index 0000000000..aa0dc05bf9 --- /dev/null +++ b/src/examples/ReactiveUI.Builder.WpfApp/Services/MockPaymentProcessor.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using ReactiveUI.Builder.WpfApp.Models; + +namespace ReactiveUI.Builder.WpfApp.Services; + +/// A fake payment processor that simulates an authorization round-trip with a small delay and canned rules. +public sealed class MockPaymentProcessor : IPaymentProcessor +{ + /// The amount above which a transaction is declined for exceeding the floor limit. + private const decimal FloorLimit = 1000m; + + /// The simulated authorization round-trip duration, in milliseconds. + private const int AuthorizationDelayMilliseconds = 1400; + + /// The number of cents in one currency unit. + private const long CentsPerUnit = 100; + + /// The cents value that triggers a canned "do not honour" decline. + private const long DeclineCents = 13; + + /// The modulus used to derive the masked last four card digits. + private const long LastFourModulus = 10_000; + + /// The modulus used to derive the six-digit authorization code. + private const long AuthModulus = 1_000_000; + + /// The card schemes a card might be issued under. + private static readonly string[] Schemes = ["VISA", "MASTERCARD", "AMEX", "EFTPOS"]; + + /// A monotonically increasing counter used to vary the fabricated card and authorization details. + private long _sequence; + + /// + public async Task AuthorizeAsync(decimal amount, CancellationToken cancellationToken) + { + await Task.Delay(AuthorizationDelayMilliseconds, cancellationToken).ConfigureAwait(true); + + var sequence = Interlocked.Increment(ref _sequence); + var cents = (long)Math.Round(amount * CentsPerUnit); + var declinedForLimit = amount > FloorLimit; + var declinedByIssuer = cents % CentsPerUnit == DeclineCents; + var approved = !declinedForLimit && !declinedByIssuer; + + return new Transaction + { + Reference = "RX" + sequence.ToString("D8", CultureInfo.InvariantCulture), + Amount = amount, + Approved = approved, + Outcome = BuildOutcome(approved, declinedForLimit, sequence), + CardScheme = Schemes[(int)(sequence % Schemes.Length)], + CardLast4 = (cents % LastFourModulus).ToString("D4", CultureInfo.InvariantCulture), + Timestamp = UtcNow(), + }; + } + + /// Builds the outcome message for a completed authorization. + /// Whether the transaction was approved. + /// Whether the decline was caused by the floor limit. + /// The transaction sequence number used to derive the authorization code. + /// The outcome message. + private static string BuildOutcome(bool approved, bool declinedForLimit, long sequence) + { + if (!approved) + { + return declinedForLimit ? "DECLINED · EXCEEDS FLOOR LIMIT" : "DECLINED · DO NOT HONOUR"; + } + + var authCode = (sequence % AuthModulus).ToString("D6", CultureInfo.InvariantCulture); + return $"APPROVED · AUTH {authCode}"; + } + + /// Gets the current UTC instant. + /// The current UTC instant. + [SuppressMessage("Major Code Smell", "S6354:Use a testable date/time provider", Justification = "Not available on all target frameworks.")] + private static DateTimeOffset UtcNow() => +#if NET8_0_OR_GREATER + TimeProvider.System.GetUtcNow(); +#else + DateTimeOffset.UtcNow; +#endif +} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Services/RoomEventKind.cs b/src/examples/ReactiveUI.Builder.WpfApp/Services/RoomEventKind.cs deleted file mode 100644 index 42d8edbe1b..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Services/RoomEventKind.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -namespace ReactiveUI.Builder.WpfApp.Services; - -/// The type of room event. -public enum RoomEventKind -{ - /// A new room was created. - Add, - - /// A room was removed. - Remove, - - /// Request others to broadcast their current rooms. - SyncRequest -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/AppBootstrapper.cs b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/AppBootstrapper.cs index 39e87600d3..62c44c9d29 100644 --- a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/AppBootstrapper.cs +++ b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/AppBootstrapper.cs @@ -3,22 +3,25 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using ReactiveUI.Builder.WpfApp.Services; +using Splat; + namespace ReactiveUI.Builder.WpfApp.ViewModels; -/// The root screen that hosts the router and bootstraps navigation for the application. +/// The root screen that owns the router and navigates to the terminal on start-up. /// /// -public class AppBootstrapper : ReactiveObject, IScreen +public sealed class AppBootstrapper : ReactiveObject, IScreen { /// Initializes a new instance of the class. public AppBootstrapper() { Router = new(); - // Navigate to Lobby on start - Router.Navigate.Execute(new LobbyViewModel(this)).Subscribe(); + var processor = Locator.Current.GetService()!; + _ = Router.Navigate.Execute(new TerminalViewModel(this, processor)).Subscribe(); } - /// Gets the Router associated with this Screen. + /// Gets the router that hosts the navigation stack. public RoutingState Router { get; } } diff --git a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/ChatRoomViewModel.cs b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/ChatRoomViewModel.cs deleted file mode 100644 index 03d6923604..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/ChatRoomViewModel.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using ReactiveUI.Builder.WpfApp.Models; - -namespace ReactiveUI.Builder.WpfApp.ViewModels; - -/// View model for a single chat room. -public class ChatRoomViewModel : ReactiveObject, IRoutableViewModel -{ - /// The chat room model whose messages this view model displays and appends to. - private readonly ChatRoom _room; - - /// The display name of the local user, used as the sender for outgoing messages. - private readonly string _user; - - /// Initializes a new instance of the class. - /// The host screen. - /// The room. - /// The user. - public ChatRoomViewModel(IScreen hostScreen, ChatRoom room, string user) - { - ArgumentExceptionHelper.ThrowIfNull(room); - HostScreen = hostScreen; - UrlPathSegment = $"room/{room.Name}"; - _room = room; - _user = user; - - var canSend = - this.WhenAnyValue( - nameof(MessageText), - txt => !string.IsNullOrWhiteSpace(txt)); - SendMessage = ReactiveCommand.Create(SendMessageImpl, canSend); - - // Observe new incoming messages via MessageBus using the room name as the contract across instances - MessageBus.Current.Listen(room.Name) - .Where(msg => msg.InstanceId != Services.AppInstance.Id) - .EmitIfQuiet(TimeSpan.FromMilliseconds(33)) - .ObserveOn(RxSchedulers.MainThreadScheduler) - .Subscribe(msg => - { - _room.Messages.Add(new() { Sender = msg.Sender, Text = msg.Text, Timestamp = msg.Timestamp }); - Trace.TraceInformation($"[Room:{room.Name}] RX '{msg.Text}' from {msg.Sender}/{msg.InstanceId}"); - }); - } - - /// - public string UrlPathSegment { get; } - - /// - public IScreen HostScreen { get; } - - /// Gets the room name. - public string RoomName => _room.Name; - - /// Gets the messages. - public IReadOnlyList Messages => _room.Messages; - - /// Gets or sets the message text. - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1500:Braces should not share line", - Justification = "C# 13 field keyword with property initializer")] - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1513:Closing brace should be followed by blank line", - Justification = "C# 13 field keyword with property initializer")] - public string MessageText - { - get => field; - set => this.RaiseAndSetIfChanged(ref field, value); - } -= string.Empty; - - /// Gets command to send a message. - public ReactiveCommand SendMessage { get; } - - /// Adds to the room, broadcasts it over the , clears the input, and backs . - [SuppressMessage("Major Code Smell", "S6354:Use a testable date/time provider", Justification = "Not available all TFMs")] - private void SendMessageImpl() - { - var msg = new ChatMessage - { - Sender = _user, - Text = MessageText, - Timestamp = -#if NET8_0_OR_GREATER - TimeProvider.System.GetUtcNow(), -#else - DateTimeOffset.Now, -#endif - }; - _room.Messages.Add(msg); - var networkMessage = new ChatNetworkMessage(_room.Id, _room.Name, msg.Sender, msg.Text, msg.Timestamp) - { - InstanceId = Services.AppInstance.Id - }; - - // Post on null contract so the network service can broadcast to other instances. - MessageBus.Current.SendMessage(networkMessage); - Trace.TraceInformation($"[Room:{_room.Name}] TX '{msg.Text}' from {_user}/{Services.AppInstance.Id}"); - - MessageText = string.Empty; - } -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/JournalViewModel.cs b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/JournalViewModel.cs new file mode 100644 index 0000000000..51d8738345 --- /dev/null +++ b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/JournalViewModel.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using ReactiveUI.Builder.WpfApp.Models; + +namespace ReactiveUI.Builder.WpfApp.ViewModels; + +/// Shows the persisted transaction journal and the running total of today's approved takings. +public sealed class JournalViewModel : ReactiveObject, IRoutableViewModel +{ + /// Backs the derived property. + private readonly ObservableAsPropertyHelper _todayTotal; + + /// Initializes a new instance of the class. + /// The screen hosting this view model. + [SuppressMessage( + "Major Code Smell", + "S3366:Make sure the use of this in constructors is safe", + Justification = "Single-threaded WPF view model fully constructed before the reactive pipelines run.")] + public JournalViewModel(IScreen hostScreen) + { + ArgumentExceptionHelper.ThrowIfNull(hostScreen); + HostScreen = hostScreen; + UrlPathSegment = "journal"; + Transactions = ((TerminalState)RxSuspension.SuspensionHost.AppState!).Journal; + + _todayTotal = Transactions.ObserveCollectionChanges() + .Select(_ => ComputeTodayTotal()) + .ToProperty(this, nameof(TodayTotal), ComputeTodayTotal()); + + GoBack = ReactiveCommand.CreateFromObservable(() => HostScreen.Router.NavigateBack.Execute()); + Clear = ReactiveCommand.Create(Transactions.Clear); + } + + /// + public string UrlPathSegment { get; } + + /// + public IScreen HostScreen { get; } + + /// Gets the persisted transactions, most recent first. + public ObservableCollection Transactions { get; } + + /// Gets the total value of today's approved transactions. + public decimal TodayTotal => _todayTotal.Value; + + /// Gets the command that navigates back to the terminal. + public ReactiveCommand GoBack { get; } + + /// Gets the command that clears the journal. + public ReactiveCommand Clear { get; } + + /// Gets the current UTC date. + /// Today's date. + [SuppressMessage("Major Code Smell", "S6354:Use a testable date/time provider", Justification = "Not available on all target frameworks.")] + private static DateTime UtcToday() => +#if NET8_0_OR_GREATER + TimeProvider.System.GetUtcNow().UtcDateTime.Date; +#else + DateTimeOffset.UtcNow.UtcDateTime.Date; +#endif + + /// Sums today's approved transactions without LINQ allocations. + /// The total value of today's approved takings. + private decimal ComputeTodayTotal() + { + var today = UtcToday(); + decimal total = 0m; + foreach (var transaction in Transactions) + { + if (transaction.Approved && transaction.Timestamp.UtcDateTime.Date == today) + { + total += transaction.Amount; + } + } + + return total; + } +} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/LobbyViewModel.cs b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/LobbyViewModel.cs deleted file mode 100644 index 3f420b4a4f..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/LobbyViewModel.cs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using ReactiveUI.Builder.WpfApp.Models; - -namespace ReactiveUI.Builder.WpfApp.ViewModels; - -/// The lobby view model which lists rooms and allows creating/joining rooms. -public class LobbyViewModel : ReactiveObject, IRoutableViewModel -{ - /// The MessageBus contract used to broadcast and listen for room events across instances. - private const string RoomsKey = "__rooms__"; - - /// Backs the output property, projecting room changes into a snapshot list. - private readonly ObservableAsPropertyHelper> _rooms; - - /// Initializes a new instance of the class. - /// The host screen. - [SuppressMessage("Reliability", "S3366:Don't expose 'this' in constructors", Justification = "OAPH/WhenAny initialization requires 'this'; single-threaded sample.")] - public LobbyViewModel(IScreen hostScreen) - { - HostScreen = hostScreen; - UrlPathSegment = "lobby"; - - var canCreate = - this.WhenAnyValue(nameof(RoomName), rn => !string.IsNullOrWhiteSpace(rn)); - CreateRoom = ReactiveCommand.Create(CreateRoomImpl, canCreate); - - DeleteRoom = ReactiveCommand.Create(DeleteRoomImpl); - - JoinRoom = ReactiveCommand.CreateFromTask(async room => - { - ArgumentExceptionHelper.ThrowIfNull(room); - await HostScreen.Router.Navigate.Execute(new ChatRoomViewModel(HostScreen, room, DisplayName)); - }); - - // Local changes - var localRoomsChanged = MessageBus.Current.Listen().Select(_ => RxVoid.Default); - - // Remote changes and sync (ignore own events) - var remoteRoomsChanged = MessageBus.Current - .Listen(RoomsKey) - .Where(m => m.InstanceId != Services.AppInstance.Id) - .Do(evt => - { - Trace.TraceInformation($"[Lobby] Room evt {evt.Kind} name='{evt.RoomName}' from={evt.InstanceId}"); - switch (evt.Kind) - { - case Services.RoomEventKind.SyncRequest: - { - // Respond with our snapshot of room names - var snapshot = GetState().Rooms.ConvertAll(r => r.Name); - var response = new RoomEventMessage(Services.RoomEventKind.Add, string.Empty) - { - Snapshot = snapshot, - InstanceId = Services.AppInstance.Id - }; - MessageBus.Current.SendMessage(response, RoomsKey); - break; - } - - default: - { - ApplyRoomEvent(evt); - break; - } - } - }) - .Select(_ => RxVoid.Default); - - RoomsChanged = Signal.Emit(RxVoid.Default) - .Concat(Signal.Blend(localRoomsChanged, remoteRoomsChanged) - .EmitIfQuiet(TimeSpan.FromMilliseconds(50), RxSchedulers.TaskpoolScheduler)); - - this.WhenAnyObservable(x => x.RoomsChanged) - .Select(_ => (IReadOnlyList)[.. GetState().Rooms]) - .ObserveOn(RxSchedulers.MainThreadScheduler) - .ToProperty(this, nameof(Rooms), out _rooms); - - // Request a snapshot from peers shortly after activation - RxSchedulers.MainThreadScheduler.Schedule(RxVoid.Default, TimeSpan.FromMilliseconds(500), (_, _) => - { - var req = new RoomEventMessage(Services.RoomEventKind.SyncRequest, string.Empty) - { - InstanceId = Services.AppInstance.Id - }; - Trace.TraceInformation("[Lobby] Broadcasting SyncRequest"); - MessageBus.Current.SendMessage(req, RoomsKey); - return EmptyDisposable.Instance; - }); - } - - /// - public string UrlPathSegment { get; } - - /// - public IScreen HostScreen { get; } - - /// Gets or sets the display name for the current user. - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1500:Braces should not share line", - Justification = "C# 13 field keyword with property initializer")] - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1513:Closing brace should be followed by blank line", - Justification = "C# 13 field keyword with property initializer")] - public string DisplayName - { - get => field; - set => this.RaiseAndSetIfChanged(ref field, value); - } -= Environment.MachineName; - - /// Gets or sets the new room name. - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1500:Braces should not share line", - Justification = "C# 13 field keyword with property initializer")] - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1513:Closing brace should be followed by blank line", - Justification = "C# 13 field keyword with property initializer")] - public string RoomName - { - get => field; - set => this.RaiseAndSetIfChanged(ref field, value); - } -= string.Empty; - - /// Gets or sets the selected combo item used by the WPF binding regression surface. - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500:Braces should not share line", Justification = "C# 13 field keyword with property initializer")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "C# 13 field keyword with property initializer")] - public ChatRoom? SelectedComboItem - { - get => field; - set => this.RaiseAndSetIfChanged(ref field, value); - } - - /// Gets the current list of rooms. - public IReadOnlyList Rooms => _rooms.Value; - - /// Gets an observable signaling when the rooms change. - public IObservable RoomsChanged { get; } - - /// Gets the command which creates a new room. - public ReactiveCommand CreateRoom { get; } - - /// Gets the command which deletes a room. - public ReactiveCommand DeleteRoom { get; } - - /// Gets the command which joins an existing room. - public ReactiveCommand JoinRoom { get; } - - /// Gets the current persisted chat state from the ReactiveUI suspension host. - /// The active instance. - private static ChatState GetState() => RxSuspension.SuspensionHost.GetAppState(); - - /// Applies a remote room event (add, remove, or snapshot) to the local chat state. - /// The room event received from another app instance. - private static void ApplyRoomEvent(RoomEventMessage evt) - { - var state = GetState(); - - if (evt.Snapshot is not null) - { - // Apply snapshot - foreach (var name in evt.Snapshot) - { - if (!state.Rooms.Exists(r => string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase))) - { - state.Rooms.Add(new() { Name = name }); - } - } - - return; - } - - switch (evt.Kind) - { - case Services.RoomEventKind.Add: - { - if (!state.Rooms.Exists(r => string.Equals(r.Name, evt.RoomName, StringComparison.OrdinalIgnoreCase))) - { - state.Rooms.Add(new() { Name = evt.RoomName }); - } - - break; - } - - case Services.RoomEventKind.Remove: - { - state.Rooms.RemoveAll(r => string.Equals(r.Name, evt.RoomName, StringComparison.OrdinalIgnoreCase)); - break; - } - } - } - - /// - /// Creates a room from the current if it does not already exist, broadcasts the - /// addition to other instances, and clears the input. Backs the command. - /// - private void CreateRoomImpl() - { - var name = RoomName.Trim(); - var state = GetState(); - var existing = state.Rooms.FirstOrDefault(r => string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase)); - if (existing is null) - { - var room = new ChatRoom { Name = name }; - state.Rooms.Add(room); - - // Broadcast room add to peers - var evt = new RoomEventMessage(Services.RoomEventKind.Add, room.Name) - { - InstanceId = Services.AppInstance.Id - }; - MessageBus.Current.SendMessage(evt, RoomsKey); - Trace.TraceInformation($"[Lobby] Created room '{room.Name}'"); - } - - MessageBus.Current.SendMessage(new ChatStateChanged()); - RoomName = string.Empty; - } - - /// Removes the supplied room from the local state and broadcasts the removal to other instances. Backs the command. - /// The room to delete. - private void DeleteRoomImpl(ChatRoom room) - { - var state = GetState(); - if (!state.Rooms.Remove(room)) - { - return; - } - - var evt = new RoomEventMessage(Services.RoomEventKind.Remove, room.Name) - { - InstanceId = Services.AppInstance.Id - }; - MessageBus.Current.SendMessage(evt, RoomsKey); - MessageBus.Current.SendMessage(new ChatStateChanged()); - Trace.TraceInformation($"[Lobby] Deleted room '{room.Name}'"); - } -} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/TerminalViewModel.cs b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/TerminalViewModel.cs new file mode 100644 index 0000000000..de8ed63f36 --- /dev/null +++ b/src/examples/ReactiveUI.Builder.WpfApp/ViewModels/TerminalViewModel.cs @@ -0,0 +1,153 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using ReactiveUI.Builder.WpfApp.Models; +using ReactiveUI.Builder.WpfApp.Services; + +namespace ReactiveUI.Builder.WpfApp.ViewModels; + +/// +/// The point-of-sale terminal. Digits are entered on a keypad to build an amount, then an asynchronous +/// (a ReactiveCommand.CreateFromTask) authorizes a mock payment, records it in the +/// persisted journal and surfaces the result. +/// +public sealed class TerminalViewModel : ReactiveObject, IRoutableViewModel +{ + /// The largest amount, in cents, the keypad will accept ($99,999.99). + private const long MaximumAmountCents = 9_999_999; + + /// The number of cents in one currency unit. + private const decimal CentsPerUnit = 100m; + + /// The base used when shifting digits onto the entered amount. + private const long Radix = 10; + + /// The processor that authorizes payments. + private readonly IPaymentProcessor _processor; + + /// Backs the derived property. + private readonly ObservableAsPropertyHelper _amount; + + /// Backs the derived property. + private readonly ObservableAsPropertyHelper _formattedAmount; + + /// Backs the derived property. + private readonly ObservableAsPropertyHelper _isBusy; + + /// Initializes a new instance of the class. + /// The screen hosting this view model. + /// The payment processor used to authorize transactions. + [SuppressMessage( + "Major Code Smell", + "S3366:Make sure the use of this in constructors is safe", + Justification = "Single-threaded WPF view model fully constructed before the reactive pipelines run.")] + public TerminalViewModel(IScreen hostScreen, IPaymentProcessor processor) + { + ArgumentExceptionHelper.ThrowIfNull(processor); + HostScreen = hostScreen; + UrlPathSegment = "terminal"; + _processor = processor; + + _amount = this.WhenAnyValue(x => x.AmountCents, static cents => cents / CentsPerUnit) + .ToProperty(this, nameof(Amount)); + + _formattedAmount = this.WhenAnyValue(x => x.Amount, static amount => amount.ToString("C", CultureInfo.CurrentCulture)) + .ToProperty(this, nameof(FormattedAmount)); + + AppendDigit = ReactiveCommand.Create(AppendDigitImpl); + Backspace = ReactiveCommand.Create(RemoveLastDigit); + Clear = ReactiveCommand.Create(ClearAmount); + + var canPay = this.WhenAnyValue(x => x.AmountCents, static cents => cents > 0); + PayCommand = ReactiveCommand.CreateFromTask(ct => _processor.AuthorizeAsync(Amount, ct), canPay); + _isBusy = PayCommand.IsExecuting.ToProperty(this, nameof(IsBusy)); + + _ = PayCommand.WitnessOn(RxSchedulers.MainThreadScheduler).Subscribe(transaction => + { + Journal.Insert(0, transaction); + LastTransaction = transaction; + AmountCents = 0; + MessageBus.Current.SendMessage(transaction); + }); + + ViewJournal = ReactiveCommand.CreateFromObservable( + () => HostScreen.Router.Navigate.Execute(new JournalViewModel(HostScreen))); + } + + /// + public string UrlPathSegment { get; } + + /// + public IScreen HostScreen { get; } + + /// Gets or sets the amount entered on the keypad, in cents. + public long AmountCents + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + + /// Gets or sets the most recently completed transaction. + public Transaction? LastTransaction + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + + /// Gets the entered amount as a currency value. + public decimal Amount => _amount.Value; + + /// Gets the entered amount formatted as currency for display. + public string FormattedAmount => _formattedAmount.Value; + + /// Gets a value indicating whether a payment is currently being authorized. + public bool IsBusy => _isBusy.Value; + + /// Gets the command that appends a pressed digit to the amount. + public ReactiveCommand AppendDigit { get; } + + /// Gets the command that removes the last entered digit. + public ReactiveCommand Backspace { get; } + + /// Gets the command that clears the entered amount. + public ReactiveCommand Clear { get; } + + /// Gets the command that authorizes the payment for the entered amount. + public ReactiveCommand PayCommand { get; } + + /// Gets the command that navigates to the transaction journal. + public ReactiveCommand ViewJournal { get; } + + /// Gets the persisted transaction journal from the current suspension state. + private static ObservableCollection Journal => + ((TerminalState)RxSuspension.SuspensionHost.AppState!).Journal; + + /// Appends a pressed keypad digit to the amount, ignoring presses that would overflow the limit. + /// The digit that was pressed. + private void AppendDigitImpl(string digit) + { + if (!long.TryParse(digit, NumberStyles.None, CultureInfo.InvariantCulture, out var value)) + { + return; + } + + var next = (AmountCents * Radix) + value; + if (next > MaximumAmountCents) + { + return; + } + + AmountCents = next; + } + + /// Removes the last entered digit from the amount. + private void RemoveLastDigit() => AmountCents /= Radix; + + /// Clears the entered amount. + private void ClearAmount() => AmountCents = 0; +} diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Views/ChatRoomView.xaml b/src/examples/ReactiveUI.Builder.WpfApp/Views/ChatRoomView.xaml deleted file mode 100644 index 41d6403fb5..0000000000 --- a/src/examples/ReactiveUI.Builder.WpfApp/Views/ChatRoomView.xaml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/examples/ReactiveUI.Builder.WpfApp/Views/TerminalView.xaml.cs b/src/examples/ReactiveUI.Builder.WpfApp/Views/TerminalView.xaml.cs new file mode 100644 index 0000000000..4633d346b0 --- /dev/null +++ b/src/examples/ReactiveUI.Builder.WpfApp/Views/TerminalView.xaml.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Windows; +using ReactiveUI.Builder.WpfApp.ViewModels; + +namespace ReactiveUI.Builder.WpfApp.Views; + +/// The payment terminal view (keypad, amount display and result banner). +public partial class TerminalView : IViewFor +{ + /// Identifies the dependency property. + public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( + nameof(ViewModel), + typeof(TerminalViewModel), + typeof(TerminalView), + new(null)); + + /// Initializes a new instance of the class. + public TerminalView() + { + InitializeComponent(); + DataContext = this; + } + + /// Gets or sets the view model that backs this view. + public TerminalViewModel? ViewModel + { + get => (TerminalViewModel?)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TerminalViewModel?)value; + } +} diff --git a/src/examples/ReactiveUI.Samples.Maui/LoginPage.xaml.cs b/src/examples/ReactiveUI.Samples.Maui/LoginPage.xaml.cs index dab7fb6e1f..d1413713df 100644 --- a/src/examples/ReactiveUI.Samples.Maui/LoginPage.xaml.cs +++ b/src/examples/ReactiveUI.Samples.Maui/LoginPage.xaml.cs @@ -17,21 +17,21 @@ public LoginPage() InitializeComponent(); ViewModel = new(RxSchedulers.MainThreadScheduler); - this.WhenActivated(d => + _ = this.WhenActivated(d => { - this.Bind(ViewModel, vm => vm.UserName, v => v.Username.Text) + _ = this.Bind(ViewModel, vm => vm.UserName, v => v.Username.Text) .DisposeWith(d); - this.Bind(ViewModel, vm => vm.Password, v => v.Password.Text) + _ = this.Bind(ViewModel, vm => vm.Password, v => v.Password.Text) .DisposeWith(d); - this.BindCommand(ViewModel, vm => vm.Login, v => v.LoginButton) + _ = this.BindCommand(ViewModel, vm => vm.Login, v => v.LoginButton) .DisposeWith(d); - this.BindCommand(ViewModel, vm => vm.Cancel, v => v.CancelButton) + _ = this.BindCommand(ViewModel, vm => vm.Cancel, v => v.CancelButton) .DisposeWith(d); - ViewModel.Login + _ = ViewModel.Login .SelectMany(success => Signal.FromAsync(async () => { await Shell.Current.DisplayAlertAsync( diff --git a/src/examples/ReactiveUI.Samples.Maui/MauiProgram.cs b/src/examples/ReactiveUI.Samples.Maui/MauiProgram.cs index be3b9ad259..7c92cda3e8 100644 --- a/src/examples/ReactiveUI.Samples.Maui/MauiProgram.cs +++ b/src/examples/ReactiveUI.Samples.Maui/MauiProgram.cs @@ -16,7 +16,7 @@ public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); - builder + _ = builder .UseMauiApp() .UseReactiveUI(rxBuilder => rxBuilder.WithMaui()); diff --git a/src/examples/ReactiveUI.Samples.Winforms/LoginView.cs b/src/examples/ReactiveUI.Samples.Winforms/LoginView.cs index c76de713aa..0b4613f883 100644 --- a/src/examples/ReactiveUI.Samples.Winforms/LoginView.cs +++ b/src/examples/ReactiveUI.Samples.Winforms/LoginView.cs @@ -50,21 +50,21 @@ public LoginView() ViewModel = new(RxSchedulers.MainThreadScheduler); - this.WhenActivated(d => + _ = this.WhenActivated(d => { - this.Bind(ViewModel, vm => vm.UserName, v => v._username.Text) + _ = this.Bind(ViewModel, vm => vm.UserName, v => v._username.Text) .DisposeWith(d); - this.Bind(ViewModel, vm => vm.Password, v => v._password.Text) + _ = this.Bind(ViewModel, vm => vm.Password, v => v._password.Text) .DisposeWith(d); - this.BindCommand(ViewModel, vm => vm.Login, v => v._login) + _ = this.BindCommand(ViewModel, vm => vm.Login, v => v._login) .DisposeWith(d); - this.BindCommand(ViewModel, vm => vm.Cancel, v => v._cancel) + _ = this.BindCommand(ViewModel, vm => vm.Cancel, v => v._cancel) .DisposeWith(d); - ViewModel.Login + _ = ViewModel.Login .Subscribe(success => MessageBox.Show( success ? "Welcome!" : "Invalid credentials.", success ? "Login Successful" : "Login Failed")) diff --git a/src/examples/ReactiveUI.Samples.Winforms/Program.cs b/src/examples/ReactiveUI.Samples.Winforms/Program.cs index 5681f03dcd..588d45387c 100644 --- a/src/examples/ReactiveUI.Samples.Winforms/Program.cs +++ b/src/examples/ReactiveUI.Samples.Winforms/Program.cs @@ -14,11 +14,12 @@ internal static class Program [STAThread] private static void Main() { - RxAppBuilder.CreateReactiveUIBuilder() + ApplicationConfiguration.Initialize(); + + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithWinForms() .BuildApp(); - ApplicationConfiguration.Initialize(); Application.Run(new MainForm()); } } diff --git a/src/examples/ReactiveUI.Samples.Wpf/App.xaml.cs b/src/examples/ReactiveUI.Samples.Wpf/App.xaml.cs index f1ca22e721..c9b7f927f9 100644 --- a/src/examples/ReactiveUI.Samples.Wpf/App.xaml.cs +++ b/src/examples/ReactiveUI.Samples.Wpf/App.xaml.cs @@ -16,7 +16,7 @@ protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithWpf() .BuildApp(); diff --git a/src/examples/ReactiveUI.Samples.Wpf/LoginView.xaml.cs b/src/examples/ReactiveUI.Samples.Wpf/LoginView.xaml.cs index 9c11adaab4..a28b81ae95 100644 --- a/src/examples/ReactiveUI.Samples.Wpf/LoginView.xaml.cs +++ b/src/examples/ReactiveUI.Samples.Wpf/LoginView.xaml.cs @@ -23,26 +23,26 @@ public LoginView() InitializeComponent(); ViewModel = new(RxSchedulers.MainThreadScheduler); - this.WhenActivated(d => + _ = this.WhenActivated(d => { - this.Bind(ViewModel, vm => vm.UserName, v => v.Username.Text) + _ = this.Bind(ViewModel, vm => vm.UserName, v => v.Username.Text) .DisposeWith(d); - this.BindCommand(ViewModel, vm => vm.Login, v => v.Login) + _ = this.BindCommand(ViewModel, vm => vm.Login, v => v.Login) .DisposeWith(d); - this.BindCommand(ViewModel, vm => vm.Cancel, v => v.Cancel) + _ = this.BindCommand(ViewModel, vm => vm.Cancel, v => v.Cancel) .DisposeWith(d); // WPF PasswordBox doesn't support data binding, so marshal changes manually. - Signal.FromEventPattern( + _ = Signal.FromEventPattern( h => Password.PasswordChanged += h, h => Password.PasswordChanged -= h) .Select(_ => Password.Password) .BindTo(this, v => v.ViewModel!.Password) .DisposeWith(d); - ViewModel.Login + _ = ViewModel.Login .Subscribe(success => MessageBox.Show( success ? "Welcome!" : "Invalid credentials.", success ? "Login Successful" : "Login Failed")) diff --git a/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs b/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs index fb64a92a68..4c89f8a8dd 100644 --- a/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs +++ b/src/tests/ReactiveUI.AOTTests/AOTCompatibilityTests.cs @@ -45,7 +45,7 @@ public async Task ReactiveCommand_Create_WorksInAOT() var executed = false; var command = ReactiveCommand.Create(() => executed = true); - command.Execute().Subscribe(); + _ = command.Execute().Subscribe(); await Assert.That(executed).IsTrue(); } @@ -58,7 +58,7 @@ public async Task ReactiveCommand_CreateWithParameter_WorksInAOT() string? result = null; var command = ReactiveCommand.Create(param => result = param); - command.Execute("test").Subscribe(); + _ = command.Execute("test").Subscribe(); await Assert.That(result).IsEqualTo("test"); } @@ -90,7 +90,7 @@ public async Task WhenAnyValue_StringPropertyNames_WorksInAOT() string? observedValue = null; // Using string property names should work in AOT - obj.WhenAnyValue(nameof(TestReactiveObject.TestProperty)) + _ = obj.WhenAnyValue(nameof(TestReactiveObject.TestProperty)) .Subscribe(value => observedValue = value); obj.TestProperty = "test value"; @@ -106,7 +106,7 @@ public async Task Interaction_WorksInAOT() var interaction = new Interaction(); var called = false; - interaction.RegisterHandler(context => + _ = interaction.RegisterHandler(context => { called = true; context.SetOutput(true); diff --git a/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs b/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs index f4f26fb16b..b03ddcea6a 100644 --- a/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs +++ b/src/tests/ReactiveUI.AOTTests/AdvancedAOTTests.cs @@ -26,7 +26,7 @@ public async Task RoutingState_Navigation_WorksInAOT() var viewModel = new TestRoutableViewModel(); // Test navigation - routingState.Navigate.Execute(viewModel).Subscribe(); + _ = routingState.Navigate.Execute(viewModel).Subscribe(); await Assert.That(routingState.NavigationStack).Count().IsEqualTo(1); await Assert.That(routingState.NavigationStack[0]).IsEqualTo(viewModel); @@ -40,7 +40,7 @@ public async Task PropertyValidation_WorksInAOT() var property = new ReactiveProperty(string.Empty, Sequencer.Immediate, false, false); var hasErrors = false; - property.ObserveValidationErrors() + _ = property.ObserveValidationErrors() .Subscribe(error => hasErrors = !string.IsNullOrEmpty(error)); _ = property.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null); @@ -61,10 +61,10 @@ public async Task ViewModelActivation_WorksInAOT() viewModel.WhenActivated(disposables => { activated = true; - new ActionDisposable(() => deactivated = true).DisposeWith(disposables); + _ = new ActionDisposable(() => deactivated = true).DisposeWith(disposables); }); - viewModel.Activator.Activate(); + _ = viewModel.Activator.Activate(); await Assert.That(activated).IsTrue(); viewModel.Activator.Deactivate(); @@ -113,7 +113,7 @@ public async Task MessageBus_Operations_WorkInAOT() var received = false; const string testMessage = "test message"; - messageBus.Listen().Subscribe(msg => received = msg == testMessage); + _ = messageBus.Listen().Subscribe(msg => received = msg == testMessage); messageBus.SendMessage(testMessage); diff --git a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs index 5ae111a6ac..a6dfbf7567 100644 --- a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs +++ b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTMarkupTests.cs @@ -47,7 +47,7 @@ public async Task ReactiveProperty_Refresh_WorksWithAOTSuppression() var property = new ReactiveProperty(InitialValue, scheduler, false, false); var values = new List(); - property.Subscribe(value => values.Add(value ?? string.Empty)); + _ = property.Subscribe(value => values.Add(value ?? string.Empty)); property.Refresh(); // This calls RaisePropertyChanged which has AOT attributes @@ -87,7 +87,7 @@ public async Task ReactiveProperty_ComprehensiveOperations_WorkWithAOT() // Test basic operations var valueChanges = new List(); - property.Subscribe(value => valueChanges.Add(value ?? string.Empty)); + _ = property.Subscribe(value => valueChanges.Add(value ?? string.Empty)); // Test value setting (uses RaisePropertyChanged) property.Value = "changed"; @@ -97,7 +97,7 @@ public async Task ReactiveProperty_ComprehensiveOperations_WorkWithAOT() // Test validation var hasErrors = false; - property.ObserveHasErrors.Subscribe(errors => hasErrors = errors); + _ = property.ObserveHasErrors.Subscribe(errors => hasErrors = errors); _ = property.AddValidationError(x => string.IsNullOrEmpty(x) ? "Required" : null); property.Value = string.Empty; @@ -128,11 +128,11 @@ public async Task MixedAOTScenario_ComplexWorkflow_WorksCorrectly() // AOT-compatible: MessageBus usage var messageBus = new MessageBus(); var messages = new List(); - messageBus.Listen().Subscribe(messages.Add); + _ = messageBus.Listen().Subscribe(messages.Add); // AOT-compatible: Interactions var interaction = new Interaction(); - interaction.RegisterHandler(context => context.SetOutput(context.Input == "test")); + _ = interaction.RegisterHandler(context => context.SetOutput(context.Input == "test")); // Test the workflow property.Value = UpdatedValue; @@ -226,14 +226,14 @@ public async Task ViewModelActivation_AOTCompatible_WorksCorrectly() // Create reactive property within activation var property = new ReactiveProperty("activated", scheduler, false, false); - property.DisposeWith(disposables); + _ = property.DisposeWith(disposables); // Setup cleanup - new ActionDisposable(() => deactivationCount++).DisposeWith(disposables); + _ = new ActionDisposable(() => deactivationCount++).DisposeWith(disposables); }); // Test activation cycle - viewModel.Activator.Activate(); + _ = viewModel.Activator.Activate(); using (Assert.Multiple()) { await Assert.That(activationCount).IsEqualTo(1); @@ -248,7 +248,7 @@ public async Task ViewModelActivation_AOTCompatible_WorksCorrectly() } // Test reactivation - viewModel.Activator.Activate(); + _ = viewModel.Activator.Activate(); using (Assert.Multiple()) { await Assert.That(activationCount).IsEqualTo(ExpectedCount); @@ -273,11 +273,11 @@ public async Task ErrorHandling_AOTScenarios_WorkCorrectly() var errors = new List(); // Subscribe to thrown exceptions (tests ReactiveObject error handling) - property.ThrownExceptions.Subscribe(errors.Add); + _ = property.ThrownExceptions.Subscribe(errors.Add); // Test validation errors var validationErrors = new List(); - property.ObserveErrorChanged + _ = property.ObserveErrorChanged .Where(errs => errs is not null) .Subscribe(errs => validationErrors.AddRange(errs!.OfType())); diff --git a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs index bdb4212663..dff6f69d77 100644 --- a/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs +++ b/src/tests/ReactiveUI.AOTTests/ComprehensiveAOTTests.cs @@ -55,7 +55,7 @@ public async Task Interactions_WorkInAOT() var interaction = new Interaction(); var result = false; - interaction.RegisterHandler(static context => context.SetOutput(context.Input == "test")); + _ = interaction.RegisterHandler(static context => context.SetOutput(context.Input == "test")); result = await interaction.Handle("test").FirstAsync(); await Assert.That(result).IsTrue(); @@ -72,7 +72,7 @@ public async Task MessageBus_WorksInAOT() var messageBus = new MessageBus(); var receivedMessages = new List(); - messageBus.Listen().ObserveOn(Sequencer.Immediate).Subscribe(receivedMessages.Add); + _ = messageBus.Listen().ObserveOn(Sequencer.Immediate).Subscribe(receivedMessages.Add); messageBus.SendMessage("Message1"); messageBus.SendMessage("Message2"); @@ -94,11 +94,11 @@ public async Task ReactiveCommand_WithProperSuppression_WorksInAOT() var command = ReactiveCommand.Create(() => executed = true, outputScheduler: Sequencer.Immediate); var canExecute = true; - command.CanExecute.ObserveOn(Sequencer.Immediate).Subscribe(canExec => canExecute = canExec); + _ = command.CanExecute.ObserveOn(Sequencer.Immediate).Subscribe(canExec => canExecute = canExec); await Assert.That(canExecute).IsTrue(); - command.Execute().ObserveOn(Sequencer.Immediate).Subscribe(); + _ = command.Execute().ObserveOn(Sequencer.Immediate).Subscribe(); await Assert.That(executed).IsTrue(); } @@ -115,7 +115,7 @@ public async Task ReactiveProperty_WithExplicitScheduler_WorksInAOT() var property = new ReactiveProperty(InitialValue, scheduler, false, false); var values = new List(); - property.ObserveOn(Sequencer.Immediate).Subscribe(value => values.Add(value ?? string.Empty)); + _ = property.ObserveOn(Sequencer.Immediate).Subscribe(value => values.Add(value ?? string.Empty)); property.Value = "changed"; @@ -184,12 +184,12 @@ public async Task ViewModelActivation_PatternWorks_InAOT() activationCount++; // Register cleanup action - new ActionDisposable(() => deactivationCount++) + _ = new ActionDisposable(() => deactivationCount++) .DisposeWith(disposables); }); // Test activation/deactivation cycle - viewModel.Activator.Activate(); + _ = viewModel.Activator.Activate(); using (Assert.Multiple()) { await Assert.That(activationCount).IsEqualTo(1); @@ -204,7 +204,7 @@ public async Task ViewModelActivation_PatternWorks_InAOT() } // Test multiple activation cycles - viewModel.Activator.Activate(); + _ = viewModel.Activator.Activate(); viewModel.Activator.Deactivate(); using (Assert.Multiple()) diff --git a/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs b/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs index e62785ea32..45e8b00139 100644 --- a/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs +++ b/src/tests/ReactiveUI.AOTTests/FinalAOTValidationTests.cs @@ -39,12 +39,12 @@ public async Task CompleteAOTCompatibleWorkflow_WorksSeamlessly() // 3. Use interactions (fully AOT-compatible) var interaction = new Interaction(); - interaction.RegisterHandler(context => context.SetOutput(context.Input.Length > MinimumInputLength)); + _ = interaction.RegisterHandler(context => context.SetOutput(context.Input.Length > MinimumInputLength)); // 4. Use message bus (fully AOT-compatible) var messageBus = new MessageBus(); var messages = new List(); - messageBus.Listen().Subscribe(messages.Add); + _ = messageBus.Listen().Subscribe(messages.Add); // 5. Test the complete workflow property.Value = "test value"; @@ -92,16 +92,16 @@ public async Task ReactiveCommand_CompleteWorkflow_WorksWithSuppression() var taskResult = string.Empty; var observableResult = string.Empty; - simpleCommand.Subscribe(r => simpleResult = r); - paramCommand.Subscribe(r => paramResult = r); - taskCommand.Subscribe(r => taskResult = r); - observableCommand.Subscribe(r => observableResult = r); + _ = simpleCommand.Subscribe(r => simpleResult = r); + _ = paramCommand.Subscribe(r => paramResult = r); + _ = taskCommand.Subscribe(r => taskResult = r); + _ = observableCommand.Subscribe(r => observableResult = r); // Execute commands and wait for completion - simpleCommand.Execute().GetAwaiter().GetResult(); - paramCommand.Execute(CommandParameterValue).GetAwaiter().GetResult(); - taskCommand.Execute().GetAwaiter().GetResult(); - observableCommand.Execute().GetAwaiter().GetResult(); + _ = simpleCommand.Execute().GetAwaiter().GetResult(); + _ = paramCommand.Execute(CommandParameterValue).GetAwaiter().GetResult(); + _ = taskCommand.Execute().GetAwaiter().GetResult(); + _ = observableCommand.Execute().GetAwaiter().GetResult(); using (Assert.Multiple()) { @@ -138,23 +138,23 @@ public async Task MixedAOTScenario_ComplexApplication_Works() // AOT-compatible: Property with explicit scheduler var property = new ReactiveProperty("initial", scheduler, false, false); - property.DisposeWith(d); + _ = property.DisposeWith(d); // AOT-incompatible but properly suppressed: ReactiveCommand var command = ReactiveCommand.Create(() => property.Value = "updated"); - command.DisposeWith(d); + _ = command.DisposeWith(d); // AOT-compatible: Interactions var interaction = new Interaction(); - interaction.RegisterHandler(ctx => ctx.SetOutput(true)); + _ = interaction.RegisterHandler(ctx => ctx.SetOutput(true)); // Execute mixed workflow - command.Execute().Subscribe(); + _ = command.Execute().Subscribe(); interactionResult = interaction.Handle(RxVoid.Default).GetAwaiter().GetResult(); propertyValue = property.Value; }); - viewModel.Activator.Activate(); + _ = viewModel.Activator.Activate(); using (Assert.Multiple()) { @@ -205,13 +205,13 @@ public async Task ErrorHandlingAndDisposal_PatternsWork_InAOT() { // Create observables with proper disposal var source = new StateSignal("test"); - source.DisposeWith(disposables); + _ = source.DisposeWith(disposables); var property = new ReactiveProperty(string.Empty, scheduler, false, false); - property.DisposeWith(disposables); + _ = property.DisposeWith(disposables); // Connect with error handling - source + _ = source .Recover(ex => Signal.Emit($"Error: {ex.Message}")) .Subscribe(value => property.Value = value) .DisposeWith(disposables); diff --git a/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs b/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs index e7c2f3651c..a6df15be03 100644 --- a/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs +++ b/src/tests/ReactiveUI.AOTTests/StringBasedObservationTests.cs @@ -40,7 +40,7 @@ public async Task ObservableForProperty_StringName_EmitsInitialThenChanges() var s = new Sample { IntValue = InitialIntValue }; var values = new List(); - s.ObservableForProperty(nameof(Sample.IntValue), false, false, true) + _ = s.ObservableForProperty(nameof(Sample.IntValue), false, false, true) .Select(static x => x.Value) .Subscribe(values.Add); @@ -63,7 +63,7 @@ public async Task ObservableForProperty_BeforeChange_EmitsBeforeSetter() var s = new Sample { IntValue = 1 }; var before = new List(); - s.ObservableForProperty(nameof(Sample.IntValue), true, true, false) + _ = s.ObservableForProperty(nameof(Sample.IntValue), true, true, false) .Select(static x => x.Value) .Subscribe(before.Add); @@ -85,7 +85,7 @@ public async Task WhenAnyValue_StringName_WorksAndIsDistinct() var s = new Sample { Name = "a" }; var values = new List(); - s.WhenAnyValue(nameof(Sample.Name)) + _ = s.WhenAnyValue(nameof(Sample.Name)) .Subscribe(v => values.Add(v!)); s.Name = "b"; @@ -107,7 +107,7 @@ public async Task WhenAnyValue_StringName_NotDistinctWhenRequested() var s = new Sample { Name = "x" }; var values = new List(); - s.WhenAnyValue(nameof(Sample.Name), false) + _ = s.WhenAnyValue(nameof(Sample.Name), false) .Subscribe(v => values.Add(v!)); s.Name = "y"; diff --git a/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs b/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs index 50637fadc2..abb207a8d3 100644 --- a/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs +++ b/src/tests/ReactiveUI.AOTTests/StringBasedSemanticsTests.cs @@ -34,7 +34,7 @@ public async Task ObservableForProperty_String_Basic_InitialAndUpdate() var obj = new TestReactiveObject(); var seen = new List(); - obj.ObservableForProperty(nameof(TestReactiveObject.TestProperty), false, false) + _ = obj.ObservableForProperty(nameof(TestReactiveObject.TestProperty), false, false) .Select(static x => x.Value) .Subscribe(seen.Add); @@ -61,7 +61,7 @@ public async Task ObservableForProperty_String_BeforeChange_FiresOldValue() var obj = new TestReactiveObject { TestProperty = "start" }; string? observed = null; - obj.ObservableForProperty(nameof(TestReactiveObject.TestProperty), true, true) + _ = obj.ObservableForProperty(nameof(TestReactiveObject.TestProperty), true, true) .Select(x => x.Value) .Subscribe(v => observed = v); @@ -82,7 +82,7 @@ public async Task WhenAnyValue_String_IsDistinct() var obj = new TestReactiveObject(); var seen = new List(); - obj.WhenAnyValue(nameof(TestReactiveObject.TestProperty)) + _ = obj.WhenAnyValue(nameof(TestReactiveObject.TestProperty)) .Subscribe(seen.Add); obj.TestProperty = "same"; @@ -109,7 +109,7 @@ public async Task WhenAnyValue_String_TupleCombine_Works() var obj = new TestReactiveObject(); var tuples = new List<(string?, string?)>(); - obj.WhenAnyValue( + _ = obj.WhenAnyValue( nameof(TestReactiveObject.TestProperty), nameof(TestReactiveObject.ComputedProperty)).Subscribe(tuples.Add); diff --git a/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs b/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs index 6249db1c67..8654dfa485 100644 --- a/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs +++ b/src/tests/ReactiveUI.AOTTests/ViewLocatorAOTMappingTests.cs @@ -21,7 +21,7 @@ public async Task Map_ResolveView_UsesAOTMappingWithContract() var locator = new DefaultViewLocator(); // Register contract-specific and default mappings - locator.Map(static () => new(), "mobile") + _ = locator.Map(static () => new(), "mobile") .Map(static () => new()); // default var viewMobile = locator.ResolveView("mobile"); @@ -41,11 +41,11 @@ public async Task Map_ResolveView_UsesAOTMappingWithContract() public async Task Unmap_RemovesMapping() { var locator = new DefaultViewLocator(); - locator.Map(static () => new(), "c1"); + _ = locator.Map(static () => new(), "c1"); await Assert.That(locator.ResolveView("c1")).IsTypeOf(); - locator.Unmap("c1"); + _ = locator.Unmap("c1"); await Assert.That(locator.ResolveView("c1")).IsNull(); } @@ -55,7 +55,7 @@ public async Task Unmap_RemovesMapping() public async Task Map_ResolveView_UsesAOTMappingWithoutContract() { var locator = new DefaultViewLocator(); - locator.Map(static () => new()); + _ = locator.Map(static () => new()); var view = locator.ResolveView(); diff --git a/src/tests/ReactiveUI.Blazor.Tests/ReactiveComponentBaseTests.cs b/src/tests/ReactiveUI.Blazor.Tests/ReactiveComponentBaseTests.cs index 14a32ca46d..73708dfca4 100644 --- a/src/tests/ReactiveUI.Blazor.Tests/ReactiveComponentBaseTests.cs +++ b/src/tests/ReactiveUI.Blazor.Tests/ReactiveComponentBaseTests.cs @@ -101,7 +101,7 @@ public TestActivatableViewModel() => this.WhenActivated(d => { IsActive = true; - new ActionDisposable(() => IsActive = false).DisposeWith(d); + _ = new ActionDisposable(() => IsActive = false).DisposeWith(d); }); /// Gets the ViewModelActivator required for activation logic. diff --git a/src/tests/ReactiveUI.Blazor.Tests/ReactiveInjectableComponentBaseTests.cs b/src/tests/ReactiveUI.Blazor.Tests/ReactiveInjectableComponentBaseTests.cs index 888b967554..b9d36d8d02 100644 --- a/src/tests/ReactiveUI.Blazor.Tests/ReactiveInjectableComponentBaseTests.cs +++ b/src/tests/ReactiveUI.Blazor.Tests/ReactiveInjectableComponentBaseTests.cs @@ -32,7 +32,7 @@ public class ReactiveInjectableComponentBaseTests : BunitContext public async Task ViewModel_Injected_Works() { var viewModel = new TestViewModel(); - Services.AddSingleton(viewModel); + _ = Services.AddSingleton(viewModel); var cut = Render(); @@ -53,7 +53,7 @@ public async Task ViewModel_Injected_Works() public async Task ViewModel_Set_Same_Value_No_Notification() { var viewModel = new TestViewModel(); - Services.AddSingleton(viewModel); + _ = Services.AddSingleton(viewModel); var cut = Render(); @@ -74,7 +74,7 @@ public async Task ViewModel_Set_Same_Value_No_Notification() public async Task IViewFor_ViewModel_Explicit_Interface_Works() { var viewModel = new TestViewModel(); - Services.AddSingleton(viewModel); + _ = Services.AddSingleton(viewModel); var cut = Render(); IViewFor viewFor = cut.Instance; @@ -96,7 +96,7 @@ public async Task IViewFor_ViewModel_Explicit_Interface_Works() public async Task Activated_Deactivated_Observables_Work() { var viewModel = new TestViewModel(); - Services.AddSingleton(viewModel); + _ = Services.AddSingleton(viewModel); var cut = Render(); var activatable = cut.Instance; @@ -111,7 +111,7 @@ public async Task Activated_Deactivated_Observables_Work() public async Task ActivatableViewModel_Works() { var viewModel = new TestActivatableViewModel(); - Services.AddSingleton(viewModel); + _ = Services.AddSingleton(viewModel); var cut = Render(); @@ -148,7 +148,7 @@ public TestActivatableViewModel() => this.WhenActivated(disposables => { IsActivated = true; - new ActionDisposable(() => IsActivated = false).DisposeWith(disposables); + _ = new ActionDisposable(() => IsActivated = false).DisposeWith(disposables); }); /// Gets the ViewModelActivator for this ViewModel. diff --git a/src/tests/ReactiveUI.Blazor.Tests/ReactiveOwningComponentBaseTests.cs b/src/tests/ReactiveUI.Blazor.Tests/ReactiveOwningComponentBaseTests.cs index cfa5227398..0012c6ace2 100644 --- a/src/tests/ReactiveUI.Blazor.Tests/ReactiveOwningComponentBaseTests.cs +++ b/src/tests/ReactiveUI.Blazor.Tests/ReactiveOwningComponentBaseTests.cs @@ -25,7 +25,7 @@ public async Task ViewModel_Change_Triggers_StateHasChanged() var viewModel = new TestViewModel(); // OwningComponentBase typically expects T to be resolvable from the service provider. - Services.AddScoped(_ => new TestViewModel()); + _ = Services.AddScoped(_ => new TestViewModel()); var cut = Render(parameters => parameters.Add(p => p.ViewModel, viewModel)); diff --git a/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs b/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs index ebda4db565..595f8f0ba2 100644 --- a/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs +++ b/src/tests/ReactiveUI.Builder.Maui.Tests/Activation/ActivationForViewFetcherTests.cs @@ -22,7 +22,7 @@ public async Task PageAndChildViewActivateAndDeactivate() { AppBuilder.ResetBuilderStateForTests(); var resolver = new ModernDependencyResolver(); - resolver.CreateReactiveUIBuilder() + _ = resolver.CreateReactiveUIBuilder() .WithPlatformServices() .WithRegistrationOnBuild(r => r.RegisterConstant(new ActivationForViewFetcher())) .BuildApp(); diff --git a/src/tests/ReactiveUI.Builder.Maui.Tests/ReactiveUIBuilderMauiTests.cs b/src/tests/ReactiveUI.Builder.Maui.Tests/ReactiveUIBuilderMauiTests.cs index 9d63be484c..08c07c0b21 100644 --- a/src/tests/ReactiveUI.Builder.Maui.Tests/ReactiveUIBuilderMauiTests.cs +++ b/src/tests/ReactiveUI.Builder.Maui.Tests/ReactiveUIBuilderMauiTests.cs @@ -21,7 +21,7 @@ public async Task WithMaui_Should_Register_Services() { AppBuilder.ResetBuilderStateForTests(); using var locator = new ModernDependencyResolver(); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithMaui() .BuildApp(); @@ -42,12 +42,12 @@ public async Task WithMauiScheduler_Should_Use_Custom_Dispatcher_When_Provided() var dispatcher = new TestDispatcher(); var builder = locator.CreateReactiveUIBuilder(); - builder.WithMauiScheduler(dispatcher); + _ = builder.WithMauiScheduler(dispatcher); await Assert.That(builder.MainThreadScheduler).IsNotNull(); var executed = false; - builder.MainThreadScheduler!.Schedule(0, (_, _) => + _ = builder.MainThreadScheduler!.Schedule(0, (_, _) => { executed = true; return EmptyDisposable.Instance; @@ -67,7 +67,7 @@ public async Task WithMauiScheduler_Should_Use_CurrentThread_When_In_Unit_Test_R using (ForceUnitTestMode()) { - builder.WithMauiScheduler(); + _ = builder.WithMauiScheduler(); await Assert.That(builder.MainThreadScheduler).IsEqualTo(Sequencer.CurrentThread); } } diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs index b75cd5fd5b..43bc7d4d26 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsNullActionTests.cs @@ -357,7 +357,7 @@ internal sealed class NullActionTestExecutor : BuilderTestExecutorBase protected override void ConfigureBuilder() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - builder.WithRegistrationOnBuild(r => + _ = builder.WithRegistrationOnBuild(r => { r.RegisterConstant(new InstanceService01()); r.RegisterConstant(new InstanceService02()); @@ -376,7 +376,7 @@ protected override void ConfigureBuilder() r.RegisterConstant(new InstanceService15()); r.RegisterConstant(new InstanceService16()); }); - builder.WithCoreServices().BuildApp(); + _ = builder.WithCoreServices().BuildApp(); } } diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity01to06.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity01to06.cs index 10722a86f1..a8e1ab6491 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity01to06.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity01to06.cs @@ -21,10 +21,10 @@ public async Task Builder_WithInstance_1_Type_invokes_action() resolver.RegisterConstant( s1, typeof(InstanceService01)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; - builder.WithInstance(s1 => captured1 = s1); + _ = builder.WithInstance(s1 => captured1 = s1); await Assert.That(captured1).IsSameReferenceAs(s1); } @@ -38,10 +38,10 @@ public async Task Builder_WithInstance_1_Type_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance(_ => invoked = true); + _ = builder.WithInstance(_ => invoked = true); await Assert.That(invoked).IsFalse(); } @@ -61,10 +61,10 @@ public async Task Extension_WithInstance_1_Type_invokes_action() resolver.RegisterConstant( s1, typeof(InstanceService01)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; - builder.WithInstance(s1 => captured1 = s1); + _ = builder.WithInstance(s1 => captured1 = s1); await Assert.That(captured1).IsSameReferenceAs(s1); } @@ -82,10 +82,10 @@ public async Task Extension_WithInstance_1_Type_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance(_ => invoked = true); + _ = builder.WithInstance(_ => invoked = true); await Assert.That(invoked).IsFalse(); } @@ -105,11 +105,11 @@ public async Task Builder_WithInstance_2_Types_invokes_action() resolver.RegisterConstant( s2, typeof(InstanceService02)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; - builder.WithInstance((s1, s2) => + _ = builder.WithInstance((s1, s2) => { captured1 = s1; captured2 = s2; @@ -128,10 +128,10 @@ public async Task Builder_WithInstance_2_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance((_, _) => invoked = true); + _ = builder.WithInstance((_, _) => invoked = true); await Assert.That(invoked).IsFalse(); } @@ -155,11 +155,11 @@ public async Task Extension_WithInstance_2_Types_invokes_action() resolver.RegisterConstant( s2, typeof(InstanceService02)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; - builder.WithInstance((s1, s2) => + _ = builder.WithInstance((s1, s2) => { captured1 = s1; captured2 = s2; @@ -182,10 +182,10 @@ public async Task Extension_WithInstance_2_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance((_, _) => invoked = true); + _ = builder.WithInstance((_, _) => invoked = true); await Assert.That(invoked).IsFalse(); } @@ -209,12 +209,12 @@ public async Task Builder_WithInstance_3_Types_invokes_action() resolver.RegisterConstant( s3, typeof(InstanceService03)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; InstanceService03? captured3 = null; - builder.WithInstance((s1, s2, s3) => + _ = builder.WithInstance((s1, s2, s3) => { captured1 = s1; captured2 = s2; @@ -235,10 +235,10 @@ public async Task Builder_WithInstance_3_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance((_, _, _) => invoked = true); + _ = builder.WithInstance((_, _, _) => invoked = true); await Assert.That(invoked).IsFalse(); } @@ -266,12 +266,12 @@ public async Task Extension_WithInstance_3_Types_invokes_action() resolver.RegisterConstant( s3, typeof(InstanceService03)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; InstanceService03? captured3 = null; - builder.WithInstance((s1, s2, s3) => + _ = builder.WithInstance((s1, s2, s3) => { captured1 = s1; captured2 = s2; @@ -296,10 +296,10 @@ public async Task Extension_WithInstance_3_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance((_, _, _) => invoked = true); + _ = builder.WithInstance((_, _, _) => invoked = true); await Assert.That(invoked).IsFalse(); } @@ -327,13 +327,13 @@ public async Task Builder_WithInstance_4_Types_invokes_action() resolver.RegisterConstant( s4, typeof(InstanceService04)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; InstanceService03? captured3 = null; InstanceService04? captured4 = null; - builder.WithInstance((s1, s2, s3, s4) => + _ = builder.WithInstance((s1, s2, s3, s4) => { captured1 = s1; captured2 = s2; @@ -356,10 +356,10 @@ public async Task Builder_WithInstance_4_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance((_, _, _, _) => + _ = builder.WithInstance((_, _, _, _) => invoked = true); await Assert.That(invoked).IsFalse(); @@ -392,13 +392,13 @@ public async Task Extension_WithInstance_4_Types_invokes_action() resolver.RegisterConstant( s4, typeof(InstanceService04)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; InstanceService03? captured3 = null; InstanceService04? captured4 = null; - builder.WithInstance((s1, s2, s3, s4) => + _ = builder.WithInstance((s1, s2, s3, s4) => { captured1 = s1; captured2 = s2; @@ -425,10 +425,10 @@ public async Task Extension_WithInstance_4_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder.WithInstance((_, _, _, _) => + _ = builder.WithInstance((_, _, _, _) => invoked = true); await Assert.That(invoked).IsFalse(); @@ -461,14 +461,14 @@ public async Task Builder_WithInstance_5_Types_invokes_action() resolver.RegisterConstant( s5, typeof(InstanceService05)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; InstanceService03? captured3 = null; InstanceService04? captured4 = null; InstanceService05? captured5 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -499,10 +499,10 @@ public async Task Builder_WithInstance_5_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -544,14 +544,14 @@ public async Task Extension_WithInstance_5_Types_invokes_action() resolver.RegisterConstant( s5, typeof(InstanceService05)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; InstanceService03? captured3 = null; InstanceService04? captured4 = null; InstanceService05? captured5 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -586,10 +586,10 @@ public async Task Extension_WithInstance_5_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -631,7 +631,7 @@ public async Task Builder_WithInstance_6_Types_invokes_action() resolver.RegisterConstant( s6, typeof(InstanceService06)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; @@ -639,7 +639,7 @@ public async Task Builder_WithInstance_6_Types_invokes_action() InstanceService04? captured4 = null; InstanceService05? captured5 = null; InstanceService06? captured6 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -673,10 +673,10 @@ public async Task Builder_WithInstance_6_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -723,7 +723,7 @@ public async Task Extension_WithInstance_6_Types_invokes_action() resolver.RegisterConstant( s6, typeof(InstanceService06)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; @@ -731,7 +731,7 @@ public async Task Extension_WithInstance_6_Types_invokes_action() InstanceService04? captured4 = null; InstanceService05? captured5 = null; InstanceService06? captured6 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -769,10 +769,10 @@ public async Task Extension_WithInstance_6_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity07to09.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity07to09.cs index ea13268f18..8ac305fd70 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity07to09.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity07to09.cs @@ -45,7 +45,7 @@ public async Task Builder_WithInstance_7_Types_invokes_action() resolver.RegisterConstant( s7, typeof(InstanceService07)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; @@ -54,7 +54,7 @@ public async Task Builder_WithInstance_7_Types_invokes_action() InstanceService05? captured5 = null; InstanceService06? captured6 = null; InstanceService07? captured7 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -91,10 +91,10 @@ public async Task Builder_WithInstance_7_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -146,7 +146,7 @@ public async Task Extension_WithInstance_7_Types_invokes_action() resolver.RegisterConstant( s7, typeof(InstanceService07)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; @@ -155,7 +155,7 @@ public async Task Extension_WithInstance_7_Types_invokes_action() InstanceService05? captured5 = null; InstanceService06? captured6 = null; InstanceService07? captured7 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -196,10 +196,10 @@ public async Task Extension_WithInstance_7_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -255,7 +255,7 @@ public async Task Builder_WithInstance_8_Types_invokes_action() resolver.RegisterConstant( s8, typeof(InstanceService08)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; @@ -265,7 +265,7 @@ public async Task Builder_WithInstance_8_Types_invokes_action() InstanceService06? captured6 = null; InstanceService07? captured7 = null; InstanceService08? captured8 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -309,10 +309,10 @@ public async Task Builder_WithInstance_8_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -373,7 +373,7 @@ public async Task Extension_WithInstance_8_Types_invokes_action() resolver.RegisterConstant( s8, typeof(InstanceService08)); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InstanceService01? captured1 = null; InstanceService02? captured2 = null; @@ -383,7 +383,7 @@ public async Task Extension_WithInstance_8_Types_invokes_action() InstanceService06? captured6 = null; InstanceService07? captured7 = null; InstanceService08? captured8 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -431,10 +431,10 @@ public async Task Extension_WithInstance_8_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -466,7 +466,7 @@ public async Task Builder_WithInstance_9_Types_invokes_action() out var s7, out var s8, out var s9); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance09( builder, @@ -514,10 +514,10 @@ public async Task Builder_WithInstance_9_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -554,7 +554,7 @@ public async Task Extension_WithInstance_9_Types_invokes_action() out var s7, out var s8, out var s9); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance09( builder, @@ -606,10 +606,10 @@ public async Task Extension_WithInstance_9_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity10to12.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity10to12.cs index 58bc98d308..1b09d6e9e8 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity10to12.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity10to12.cs @@ -29,7 +29,7 @@ public async Task Builder_WithInstance_10_Types_invokes_action() out var s8, out var s9, out var s10); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance10( builder, @@ -80,10 +80,10 @@ public async Task Builder_WithInstance_10_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -122,7 +122,7 @@ public async Task Extension_WithInstance_10_Types_invokes_action() out var s8, out var s9, out var s10); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance10( builder, @@ -177,10 +177,10 @@ public async Task Extension_WithInstance_10_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -216,7 +216,7 @@ public async Task Builder_WithInstance_11_Types_invokes_action() out var s9, out var s10, out var s11); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance11( builder, @@ -270,10 +270,10 @@ public async Task Builder_WithInstance_11_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -314,7 +314,7 @@ public async Task Extension_WithInstance_11_Types_invokes_action() out var s9, out var s10, out var s11); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance11( builder, @@ -372,10 +372,10 @@ public async Task Extension_WithInstance_11_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -413,7 +413,7 @@ public async Task Builder_WithInstance_12_Types_invokes_action() out var s10, out var s11, out var s12); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance12( builder, @@ -470,10 +470,10 @@ public async Task Builder_WithInstance_12_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -516,7 +516,7 @@ public async Task Extension_WithInstance_12_Types_invokes_action() out var s10, out var s11, out var s12); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance12( builder, @@ -577,10 +577,10 @@ public async Task Extension_WithInstance_12_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity13to14.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity13to14.cs index 366d91b1c2..dc61fb0c47 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity13to14.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity13to14.cs @@ -32,7 +32,7 @@ public async Task Builder_WithInstance_13_Types_invokes_action() out var s11, out var s12, out var s13); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance13( builder, @@ -92,10 +92,10 @@ public async Task Builder_WithInstance_13_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -141,7 +141,7 @@ public async Task Extension_WithInstance_13_Types_invokes_action() out var s11, out var s12, out var s13); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance13( builder, @@ -205,10 +205,10 @@ public async Task Extension_WithInstance_13_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -251,7 +251,7 @@ public async Task Builder_WithInstance_14_Types_invokes_action() out var s12, out var s13, out var s14); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance14( builder, @@ -314,10 +314,10 @@ public async Task Builder_WithInstance_14_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -364,7 +364,7 @@ public async Task Extension_WithInstance_14_Types_invokes_action() out var s12, out var s13, out var s14); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance14( builder, @@ -431,10 +431,10 @@ public async Task Extension_WithInstance_14_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity15to16.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity15to16.cs index 8ad5e14397..47746ebc35 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity15to16.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.Arity15to16.cs @@ -34,7 +34,7 @@ public async Task Builder_WithInstance_15_Types_invokes_action() out var s13, out var s14, out var s15); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance15( builder, @@ -100,10 +100,10 @@ public async Task Builder_WithInstance_15_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -152,7 +152,7 @@ public async Task Extension_WithInstance_15_Types_invokes_action() out var s13, out var s14, out var s15); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance15( builder, @@ -222,10 +222,10 @@ public async Task Extension_WithInstance_15_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -271,7 +271,7 @@ public async Task Builder_WithInstance_16_Types_invokes_action() out var s14, out var s15, out var s16); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance16( builder, @@ -340,10 +340,10 @@ public async Task Builder_WithInstance_16_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -394,7 +394,7 @@ public async Task Extension_WithInstance_16_Types_invokes_action() out var s14, out var s15, out var s16); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); InvokeWithInstance16( builder, @@ -467,10 +467,10 @@ public async Task Extension_WithInstance_16_Types_skips_when_null() var builder = new ReactiveUIBuilder( resolver, null); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var invoked = false; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.HelpersInvoke.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.HelpersInvoke.cs index 5b8148b9e2..523c157d9b 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.HelpersInvoke.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderInstanceMixinsTests.HelpersInvoke.cs @@ -44,7 +44,7 @@ private static void InvokeWithInstance09( InstanceService07? c7 = null; InstanceService08? c8 = null; InstanceService09? c9 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -116,7 +116,7 @@ private static void InvokeWithInstance10( InstanceService08? c8 = null; InstanceService09? c9 = null; InstanceService10? c10 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -194,7 +194,7 @@ private static void InvokeWithInstance11( InstanceService09? c9 = null; InstanceService10? c10 = null; InstanceService11? c11 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -278,7 +278,7 @@ private static void InvokeWithInstance12( InstanceService10? c10 = null; InstanceService11? c11 = null; InstanceService12? c12 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -368,7 +368,7 @@ private static void InvokeWithInstance13( InstanceService11? c11 = null; InstanceService12? c12 = null; InstanceService13? c13 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -464,7 +464,7 @@ private static void InvokeWithInstance14( InstanceService12? c12 = null; InstanceService13? c13 = null; InstanceService14? c14 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -566,7 +566,7 @@ private static void InvokeWithInstance15( InstanceService13? c13 = null; InstanceService14? c14 = null; InstanceService15? c15 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, @@ -674,7 +674,7 @@ private static void InvokeWithInstance16( InstanceService14? c14 = null; InstanceService15? c15 = null; InstanceService16? c16 = null; - builder + _ = builder .WithInstance< InstanceService01, InstanceService02, diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs index 28ce8b2b1c..2fe669f739 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderMixinsTests.cs @@ -53,7 +53,7 @@ public void Builder_extension_methods_throw_when_builder_null() foreach (var action in NullBuilderCases) { ArgumentNullException.ThrowIfNull(action); - Assert.Throws(action); + _ = Assert.Throws(action); } } @@ -68,8 +68,8 @@ public async Task WithTaskPoolScheduler_sets_scheduler_and_rx_schedulers() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var scheduler = Sequencer.Immediate; - builder.WithTaskPoolScheduler(scheduler); - builder.WithCoreServices().Build(); + _ = builder.WithTaskPoolScheduler(scheduler); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -94,8 +94,8 @@ public async Task WithMainThreadScheduler_sets_scheduler_and_rx_schedulers() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var scheduler = Sequencer.Immediate; - builder.WithMainThreadScheduler(scheduler); - builder.WithCoreServices().Build(); + _ = builder.WithMainThreadScheduler(scheduler); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -116,8 +116,8 @@ public async Task WithRegistrationOnBuild_registers_service_when_building() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.WithRegistrationOnBuild(builder, r => r.RegisterConstant("mixins", typeof(string))); - builder.WithCoreServices().Build(); + _ = BuilderMixins.WithRegistrationOnBuild(builder, r => r.RegisterConstant("mixins", typeof(string))); + _ = builder.WithCoreServices().Build(); await Assert.That(Locator.Current.GetService()).IsEqualTo("mixins"); } @@ -130,7 +130,7 @@ public async Task WithRegistration_registers_service_immediately() const int ExpectedRegisteredValue = 42; var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.WithRegistration(builder, r => r.RegisterConstant(ExpectedRegisteredValue, typeof(int))); + _ = BuilderMixins.WithRegistration(builder, r => r.RegisterConstant(ExpectedRegisteredValue, typeof(int))); await Assert.That(Locator.Current.GetService()).IsEqualTo(ExpectedRegisteredValue); } @@ -142,8 +142,8 @@ public async Task WithViewsFromAssembly_registers_views_in_resolver() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.WithViewsFromAssembly(builder, typeof(BuilderMixinsTestView).Assembly); - builder.WithCoreServices().Build(); + _ = BuilderMixins.WithViewsFromAssembly(builder, typeof(BuilderMixinsTestView).Assembly); + _ = builder.WithCoreServices().Build(); var view = Locator.Current.GetService>(); await Assert.That(view).IsTypeOf(); @@ -156,8 +156,8 @@ public async Task WithPlatformModule_registers_module_types() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.WithPlatformModule(builder); - builder.WithCoreServices().Build(); + _ = BuilderMixins.WithPlatformModule(builder); + _ = builder.WithCoreServices().Build(); await Assert.That(Locator.Current.GetService()).IsNotNull(); } @@ -170,8 +170,8 @@ public async Task UsingSplatModule_invokes_module_registration() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var module = new TestSplatModule(); - BuilderMixins.UsingSplatModule(builder, module); - builder.WithCoreServices().Build(); + _ = BuilderMixins.UsingSplatModule(builder, module); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -188,7 +188,7 @@ public async Task UsingSplatBuilder_Executes_Callback() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var invoked = false; - BuilderMixins.UsingSplatBuilder(builder, _ => invoked = true); + _ = BuilderMixins.UsingSplatBuilder(builder, _ => invoked = true); await Assert.That(invoked).IsTrue(); } @@ -213,11 +213,11 @@ public async Task ForCustomPlatform_sets_scheduler_and_platform_registrations() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var scheduler = Sequencer.Immediate; - BuilderMixins.ForCustomPlatform( + _ = BuilderMixins.ForCustomPlatform( builder, scheduler, r => r.RegisterConstant(new PlatformRegistrationMarker(), typeof(PlatformRegistrationMarker))); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -234,7 +234,7 @@ public async Task ForPlatforms_invokes_all_actions() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var executed = new List(); - BuilderMixins.ForPlatforms( + _ = BuilderMixins.ForPlatforms( builder, b => { @@ -258,8 +258,8 @@ public async Task ConfigureMessageBus_registers_configured_instance() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var configured = false; - BuilderMixins.WithMessageBus(builder, _ => configured = true); - builder.WithCoreServices().Build(); + _ = BuilderMixins.WithMessageBus(builder, _ => configured = true); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -276,8 +276,8 @@ public async Task ConfigureViewLocator_registers_configured_locator() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var configured = false; - BuilderMixins.ConfigureViewLocator(builder, _ => configured = true); - builder.WithCoreServices().Build(); + _ = BuilderMixins.ConfigureViewLocator(builder, _ => configured = true); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -296,8 +296,8 @@ public async Task ConfigureSuspensionDriver_invokes_action_when_driver_registere Locator.CurrentMutable.RegisterConstant(driver, typeof(ISuspensionDriver)); ISuspensionDriver? observed = null; - BuilderMixins.ConfigureSuspensionDriver(builder, d => observed = d); - builder.WithCoreServices().Build(); + _ = BuilderMixins.ConfigureSuspensionDriver(builder, d => observed = d); + _ = builder.WithCoreServices().Build(); await Assert.That(observed).IsSameReferenceAs(driver); } @@ -309,7 +309,7 @@ public async Task RegisterViewModel_registers_transient_view_model() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.RegisterViewModel(builder); + _ = BuilderMixins.RegisterViewModel(builder); var first = Locator.Current.GetService(); var second = Locator.Current.GetService(); @@ -329,7 +329,7 @@ public async Task RegisterSingletonViewModel_registers_singleton_instance() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.RegisterSingletonViewModel(builder); + _ = BuilderMixins.RegisterSingletonViewModel(builder); var first = Locator.Current.GetService(); var second = Locator.Current.GetService(); @@ -348,7 +348,7 @@ public async Task RegisterView_registers_transient_view() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.RegisterView(builder); + _ = BuilderMixins.RegisterView(builder); var first = Locator.Current.GetService>(); var second = Locator.Current.GetService>(); @@ -368,7 +368,7 @@ public async Task RegisterSingletonView_registers_singleton_view() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - BuilderMixins.RegisterSingletonView(builder); + _ = BuilderMixins.RegisterSingletonView(builder); var first = Locator.Current.GetService>(); var second = Locator.Current.GetService>(); @@ -390,8 +390,8 @@ public async Task WithInstance_SingleParameter_InvokesActionWithResolvedInstance Locator.CurrentMutable.RegisterConstant("test-value", typeof(string)); string? captured = null; - builder.WithInstance(value => captured = value); - builder.WithCoreServices().Build(); + _ = builder.WithInstance(value => captured = value); + _ = builder.WithCoreServices().Build(); await Assert.That(captured).IsEqualTo("test-value"); } @@ -408,12 +408,12 @@ public async Task WithInstance_TwoParameters_InvokesActionWithBothInstances() string? capturedString = null; int? capturedInt = null; - builder.WithInstance((s, i) => + _ = builder.WithInstance((s, i) => { capturedString = s; capturedInt = i; }); - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -454,7 +454,7 @@ public async Task RegisterViews_WithViewLocator_ReturnsBuilder() var builder = RxAppBuilder.CreateReactiveUIBuilder(); // WithCoreServices registers the DefaultViewLocator - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); var result = builder.RegisterViews(views => views.Map()); @@ -477,7 +477,7 @@ public async Task WithViewModule_ReturnsBuilder() var builder = RxAppBuilder.CreateReactiveUIBuilder(); // WithCoreServices registers the DefaultViewLocator - builder.WithCoreServices().Build(); + _ = builder.WithCoreServices().Build(); var result = builder.WithViewModule(); @@ -511,7 +511,7 @@ public async Task BuildApp_WithNonReactiveUIBuilder_ThrowsInvalidOperationExcept public async Task BuildApp_WithReactiveUIBuilder_BuildsSuccessfully() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var result = BuilderMixins.BuildApp(builder); @@ -537,8 +537,8 @@ public async Task WithMessageBus_RegistersCustomMessageBus() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var customMessageBus = new MessageBus(); - BuilderMixins.WithMessageBus(builder, customMessageBus); - builder.WithCoreServices().Build(); + _ = BuilderMixins.WithMessageBus(builder, customMessageBus); + _ = builder.WithCoreServices().Build(); var registered = Locator.Current.GetService(); await Assert.That(registered).IsSameReferenceAs(customMessageBus); @@ -559,8 +559,8 @@ public async Task WithTaskPoolScheduler_WithSetRxAppFalse_DoesNotSetRxSchedulers var builder = RxAppBuilder.CreateReactiveUIBuilder(); var scheduler = Sequencer.Immediate; - builder.WithTaskPoolScheduler(scheduler, false); - builder.WithCoreServices().Build(); + _ = builder.WithTaskPoolScheduler(scheduler, false); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -591,8 +591,8 @@ public async Task WithMainThreadScheduler_WithSetRxAppFalse_DoesNotSetRxSchedule var builder = RxAppBuilder.CreateReactiveUIBuilder(); var scheduler = Sequencer.Immediate; - builder.WithMainThreadScheduler(scheduler, false); - builder.WithCoreServices().Build(); + _ = builder.WithMainThreadScheduler(scheduler, false); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { diff --git a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs index b48e326273..1c067df62a 100644 --- a/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/Mixins/BuilderSchedulerMixinsTests.cs @@ -17,7 +17,7 @@ public class BuilderSchedulerMixinsTests public void WithTaskPoolScheduler_Throws_When_Builder_Null() { var scheduler = Sequencer.Immediate; - Assert.Throws(() => BuilderMixins.WithTaskPoolScheduler(null!, scheduler)); + _ = Assert.Throws(() => BuilderMixins.WithTaskPoolScheduler(null!, scheduler)); } /// Verifies that setting the main thread scheduler on a null builder throws . @@ -25,7 +25,7 @@ public void WithTaskPoolScheduler_Throws_When_Builder_Null() public void WithMainThreadScheduler_Throws_When_Builder_Null() { var scheduler = Sequencer.Immediate; - Assert.Throws(() => BuilderMixins.WithMainThreadScheduler(null!, scheduler)); + _ = Assert.Throws(() => BuilderMixins.WithMainThreadScheduler(null!, scheduler)); } /// Verifies that setting the task pool scheduler updates both the builder and . @@ -40,8 +40,8 @@ public async Task WithTaskPoolScheduler_Sets_Scheduler_And_Rx_Schedulers() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var scheduler = Sequencer.Immediate; - builder.WithTaskPoolScheduler(scheduler); - builder.WithCoreServices().Build(); + _ = builder.WithTaskPoolScheduler(scheduler); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { @@ -67,8 +67,8 @@ public async Task WithMainThreadScheduler_Sets_Scheduler_And_Rx_Schedulers() var builder = RxAppBuilder.CreateReactiveUIBuilder(); var scheduler = Sequencer.Immediate; - builder.WithMainThreadScheduler(scheduler); - builder.WithCoreServices().Build(); + _ = builder.WithMainThreadScheduler(scheduler); + _ = builder.WithCoreServices().Build(); using (Assert.Multiple()) { diff --git a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderConverterTests.cs b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderConverterTests.cs index 51d499c215..8958e226e4 100644 --- a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderConverterTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderConverterTests.cs @@ -31,7 +31,7 @@ public void WithConverter_TypedConverter_WithNull_Throws() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithConverter((BindingTypeConverter)null!)); } @@ -54,7 +54,7 @@ public void WithConverter_GenericInterface_WithNull_Throws() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithConverter((IBindingTypeConverter)null!)); } @@ -76,7 +76,7 @@ public void WithConverter_TypedFactory_WithNull_Throws() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithConverter((Func>)null!)); } @@ -98,7 +98,7 @@ public void WithConverter_InterfaceFactory_WithNull_Throws() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithConverter((Func)null!)); } @@ -108,7 +108,7 @@ public void WithConvertersFrom_WithNull_Throws() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithConvertersFrom(null!)); } @@ -144,7 +144,7 @@ public void WithFallbackConverter_Instance_WithNull_Throws() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithFallbackConverter((IBindingFallbackConverter)null!)); } @@ -166,7 +166,7 @@ public void WithFallbackConverter_Factory_WithNull_Throws() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithFallbackConverter((Func)null!)); } diff --git a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderCoreTests.cs b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderCoreTests.cs index 6f2d408190..03922606c9 100644 --- a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderCoreTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderCoreTests.cs @@ -71,7 +71,7 @@ public async Task Build_Should_Always_Register_Core_Services() public void WithCustomRegistration_With_Null_Action_Should_Throw() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => builder.WithCustomRegistration(null!)); + _ = Assert.Throws(() => builder.WithCustomRegistration(null!)); } /// Verifies that registering views from an assembly completes without error. @@ -90,7 +90,7 @@ public async Task WithViewsFromAssembly_Should_Register_Views() public void WithViewsFromAssembly_With_Null_Assembly_Should_Throw() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => builder.WithViewsFromAssembly(null!)); + _ = Assert.Throws(() => builder.WithViewsFromAssembly(null!)); } /// Verifies that calling core services multiple times does not duplicate registrations. @@ -141,7 +141,7 @@ public async Task Build_Should_Return_ReactiveInstance_And_Initialize_ReactiveUI RxAppBuilder.ResetForTesting(); var builder = RxAppBuilder.CreateReactiveUIBuilder(); - builder.WithCoreServices(); + _ = builder.WithCoreServices(); var app = builder.Build(); @@ -174,7 +174,7 @@ public void ForPlatforms_With_Null_Array_Should_Throw() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => builder.ForPlatforms(null!)); + _ = Assert.Throws(() => builder.ForPlatforms(null!)); } /// Executor that builds the app with platform services registered. @@ -197,7 +197,7 @@ internal sealed class WithCustomRegistrationExecutor : BuilderTestExecutorBase protected override void ConfigureBuilder() { CustomServiceRegistered = false; - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithCustomRegistration(r => { r.RegisterConstant("TestValue", typeof(string)); @@ -229,7 +229,7 @@ internal sealed class FluentChainingExecutor : BuilderTestExecutorBase protected override void ConfigureBuilder() { CustomServiceRegistered = false; - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithCoreServices() .WithCustomRegistration(r => { diff --git a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderRxAppMigrationTests.cs b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderRxAppMigrationTests.cs index db2e4bdd82..e47ae5c86b 100644 --- a/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderRxAppMigrationTests.cs +++ b/src/tests/ReactiveUI.Builder.Tests/ReactiveUIBuilderRxAppMigrationTests.cs @@ -77,7 +77,7 @@ public void WithExceptionHandler_With_Null_Handler_Should_Throw() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => builder.WithExceptionHandler(null!)); + _ = Assert.Throws(() => builder.WithExceptionHandler(null!)); } /// Verifies that the non-generic suspension host creates a default . @@ -118,16 +118,16 @@ public void WithCacheSizes_With_Zero_Or_Negative_Values_Should_Throw() { var builder = RxAppBuilder.CreateReactiveUIBuilder(); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithCacheSizes(InvalidCacheSize, ValidCacheSize)); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithCacheSizes(ValidCacheSize, InvalidCacheSize)); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithCacheSizes(NegativeCacheSize, ValidCacheSize)); - Assert.Throws(() => + _ = Assert.Throws(() => builder.WithCacheSizes(ValidCacheSize, NegativeCacheSize)); } @@ -241,7 +241,7 @@ protected override void ConfigureBuilder() CapturedEx = null; var customHandler = Witness.Create(ex => CapturedEx = ex); - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithExceptionHandler(customHandler) .WithCoreServices() .BuildApp(); @@ -293,7 +293,7 @@ protected override void ConfigureBuilder() CapturedEx = null; var customHandler = Witness.Create(ex => CapturedEx = ex); - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithExceptionHandler(customHandler) .WithSuspensionHost() .WithCacheSizes(ChainedSmallCacheSize, ChainedBigCacheSize) @@ -320,7 +320,7 @@ protected override void ConfigureBuilder() var firstHandler = Witness.Create(ex => FirstCaptured = ex); var secondHandler = Witness.Create(ex => SecondCaptured = ex); - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithExceptionHandler(firstHandler) .WithExceptionHandler(secondHandler) .WithCoreServices() diff --git a/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs b/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs index 48db0a376b..02b0266dc6 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTest.cs @@ -128,7 +128,7 @@ public async Task GetActivationForView_ICanActivate_EmitsActivationChanges() var view = new TestCanActivateView(); var results = new List(); - fetcher.GetActivationForView(view).Subscribe(results.Add); + _ = fetcher.GetActivationForView(view).Subscribe(results.Add); view.ActivateSubject.OnNext(RxVoid.Default); await Task.Delay(ActivationDelayMilliseconds); diff --git a/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTests.cs b/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTests.cs index 4844cddc5e..da947e37f1 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTests.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ActivationForViewFetcherTests.cs @@ -131,7 +131,7 @@ public async Task GetActivationForView_ICanActivate_EmitsActivationStates() var activation = fetcher.GetActivationForView(view); var values = new List(); - activation.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = activation.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); activatedSubject.OnNext(RxVoid.Default); deactivatedSubject.OnNext(RxVoid.Default); diff --git a/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs b/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs index 77762e922f..e06afcc6f7 100644 --- a/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/AutoSuspendHelperTest.cs @@ -40,7 +40,7 @@ public async Task OnCreate_TriggersIsLaunchingNew() var helper = new AutoSuspendHelper(); var triggered = false; - RxSuspension.SuspensionHost.IsLaunchingNew.Subscribe(_ => triggered = true); + _ = RxSuspension.SuspensionHost.IsLaunchingNew.Subscribe(_ => triggered = true); helper.OnCreate(); await Assert.That(triggered).IsTrue(); @@ -54,7 +54,7 @@ public async Task OnStart_TriggersIsUnpausing() var helper = new AutoSuspendHelper(); var triggered = false; - RxSuspension.SuspensionHost.IsUnpausing.Subscribe(_ => triggered = true); + _ = RxSuspension.SuspensionHost.IsUnpausing.Subscribe(_ => triggered = true); helper.OnStart(); await Assert.That(triggered).IsTrue(); @@ -68,7 +68,7 @@ public async Task OnResume_TriggersIsResuming() var helper = new AutoSuspendHelper(); var triggered = false; - RxSuspension.SuspensionHost.IsResuming.Subscribe(_ => triggered = true); + _ = RxSuspension.SuspensionHost.IsResuming.Subscribe(_ => triggered = true); helper.OnResume(); await Assert.That(triggered).IsTrue(); @@ -82,7 +82,7 @@ public async Task OnSleep_TriggersShouldPersistState() var helper = new AutoSuspendHelper(); var triggered = false; - RxSuspension.SuspensionHost.ShouldPersistState.Subscribe(_ => triggered = true); + _ = RxSuspension.SuspensionHost.ShouldPersistState.Subscribe(_ => triggered = true); helper.OnSleep(); await Assert.That(triggered).IsTrue(); diff --git a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSequencerTest.cs b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSequencerTest.cs index 55c1185cef..00cdc6f304 100644 --- a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSequencerTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiDispatcherSequencerTest.cs @@ -18,12 +18,12 @@ public async Task Dispatcher_ImmediateSchedule_ExecutesWork() RxAppBuilder.ResetForTesting(); var dispatcher = new TestDispatcher(); var builder = RxAppBuilder.CreateReactiveUIBuilder(); - builder.WithMauiScheduler(dispatcher); - builder.WithCoreServices(); - builder.BuildApp(); + _ = builder.WithMauiScheduler(dispatcher); + _ = builder.WithCoreServices(); + _ = builder.BuildApp(); var executed = false; - RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); + _ = RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); await Assert.That(executed).IsTrue(); } @@ -36,12 +36,12 @@ public async Task Dispatcher_NoDispatchRequired_ExecutesImmediately() RxAppBuilder.ResetForTesting(); var dispatcher = new TestDispatcher { IsDispatchRequired = false }; var builder = RxAppBuilder.CreateReactiveUIBuilder(); - builder.WithMauiScheduler(dispatcher); - builder.WithCoreServices(); - builder.BuildApp(); + _ = builder.WithMauiScheduler(dispatcher); + _ = builder.WithCoreServices(); + _ = builder.BuildApp(); var executed = false; - RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); + _ = RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); await Assert.That(executed).IsTrue(); } @@ -54,18 +54,18 @@ public async Task Dispatcher_DispatchRequired_ExecutesWork() RxAppBuilder.ResetForTesting(); var dispatcher = new TestDispatcher { IsDispatchRequired = true }; var builder = RxAppBuilder.CreateReactiveUIBuilder(); - builder.WithMauiScheduler(dispatcher); - builder.WithCoreServices(); - builder.BuildApp(); + _ = builder.WithMauiScheduler(dispatcher); + _ = builder.WithCoreServices(); + _ = builder.BuildApp(); var executed = false; - RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); + _ = RxSchedulers.MainThreadScheduler.Schedule(() => executed = true); await Assert.That(executed).IsTrue(); } /// Test dispatcher for sequencer testing. - private sealed class TestDispatcher : Microsoft.Maui.Dispatching.IDispatcher + private sealed class TestDispatcher : IDispatcher { /// public bool IsDispatchRequired { get; set; } @@ -85,10 +85,10 @@ public bool DispatchDelayed(TimeSpan delay, Action action) } /// - public Microsoft.Maui.Dispatching.IDispatcherTimer CreateTimer() => new TestDispatcherTimer(); + public IDispatcherTimer CreateTimer() => new TestDispatcherTimer(); /// Test dispatcher timer for testing. - private sealed class TestDispatcherTimer : Microsoft.Maui.Dispatching.IDispatcherTimer + private sealed class TestDispatcherTimer : IDispatcherTimer { /// public event EventHandler? Tick; diff --git a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs index 9bb803acea..2a50077ebf 100644 --- a/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/Builder/MauiReactiveUIBuilderExtensionsTest.cs @@ -137,7 +137,7 @@ public async Task WithMaui_ConfiguresBuilder() } /// Test dispatcher for testing. - private sealed class TestDispatcher : Microsoft.Maui.Dispatching.IDispatcher + private sealed class TestDispatcher : IDispatcher { /// public bool IsDispatchRequired => false; @@ -157,11 +157,11 @@ public bool DispatchDelayed(TimeSpan delay, Action action) } /// - public Microsoft.Maui.Dispatching.IDispatcherTimer CreateTimer() => new TestDispatcherTimer(); + public IDispatcherTimer CreateTimer() => new TestDispatcherTimer(); } /// Test dispatcher timer for testing. - private sealed class TestDispatcherTimer : Microsoft.Maui.Dispatching.IDispatcherTimer + private sealed class TestDispatcherTimer : IDispatcherTimer { /// public event EventHandler? Tick; diff --git a/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs b/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs index e5747f2a4a..5cb0883178 100644 --- a/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/MauiReactiveUIBuilderExtensionsTest.cs @@ -5,6 +5,7 @@ using ReactiveUI.Builder; using Splat; +using TUnit.Core.Executors; namespace ReactiveUI.Maui.Tests; @@ -17,6 +18,38 @@ public class MauiReactiveUIBuilderExtensionsTest public async Task MauiMainThreadScheduler_IsNotNull() => await Assert.That(MauiReactiveUIBuilderExtensions.MauiMainThreadScheduler).IsNotNull(); + /// Tests that MauiMainThreadScheduler resolves to a dispatcher-backed sequencer when a current-thread dispatcher is available. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task MauiMainThreadScheduler_WithCurrentDispatcher_ResolvesDispatcherSequencer() + { + // The MauiTestExecutor installs a TestDispatcherProvider whose GetForCurrentThread() returns a dispatcher, + // so the property takes the dispatcher.ToSequencer() branch instead of falling back to Sequencer.Default. + var scheduler = MauiReactiveUIBuilderExtensions.MauiMainThreadScheduler; + + await Assert.That(scheduler.GetType().Name).IsEqualTo("MauiDispatcherSequencer"); + } + + /// Tests that WithMauiScheduler (no dispatcher) resolves the MAUI main-thread scheduler when not in a unit test runner. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task WithMauiScheduler_NotInUnitTestRunner_UsesMauiMainThreadScheduler() + { + var resolver = new ModernDependencyResolver(); + var builder = new ReactiveUIBuilder(resolver, resolver); + + using (ForceNonUnitTestMode()) + { + // With no dispatcher and outside a unit test runner, ResolveMainThreadScheduler returns the + // MauiMainThreadScheduler (the TestDispatcherProvider supplies a current-thread dispatcher). + _ = builder.WithMauiScheduler(); + + await Assert.That(builder.MainThreadScheduler!.GetType().Name).IsEqualTo("MauiDispatcherSequencer"); + } + } + /// Tests that UseReactiveUI with action does not throw. /// A representing the asynchronous operation. [Test] @@ -35,7 +68,7 @@ public async Task UseReactiveUI_WithAction_DoesNotThrow() [Test] public async Task UseReactiveUI_NullBuilder_Throws() { - const Microsoft.Maui.Hosting.MauiAppBuilder builder = null!; + const MauiAppBuilder builder = null!; await Assert.That(() => builder.UseReactiveUI(rxBuilder => { })) .Throws(); @@ -82,7 +115,7 @@ public async Task UseReactiveUI_WithDispatcher_DoesNotThrow() [Test] public async Task UseReactiveUI_WithDispatcher_NullBuilder_Throws() { - const Microsoft.Maui.Hosting.MauiAppBuilder builder = null!; + const MauiAppBuilder builder = null!; var dispatcher = new MockDispatcher(); await Assert.That(() => builder.UseReactiveUI(dispatcher)) @@ -98,7 +131,7 @@ public async Task WithMauiScheduler_RegistersScheduler() var builder = new ReactiveUIBuilder(resolver, resolver); var dispatcher = new MockDispatcher(); - builder.WithMauiScheduler(dispatcher); + _ = builder.WithMauiScheduler(dispatcher); await Assert.That(builder.MainThreadScheduler).IsNotNull(); await Assert.That(builder.MainThreadScheduler!.GetType().Name).IsEqualTo("MauiDispatcherSequencer"); @@ -112,11 +145,11 @@ public async Task MauiDispatcherSequencer_Schedule_DispatchesAction() var resolver = new ModernDependencyResolver(); var builder = new ReactiveUIBuilder(resolver, resolver); var dispatcher = new MockDispatcher(); - builder.WithMauiScheduler(dispatcher); + _ = builder.WithMauiScheduler(dispatcher); var scheduler = builder.MainThreadScheduler!; var executed = false; - scheduler.Schedule(() => executed = true); + _ = scheduler.Schedule(() => executed = true); // MockDispatcher executes immediately if Dispatch is called await Assert.That(executed).IsTrue(); @@ -131,11 +164,11 @@ public async Task MauiDispatcherSequencer_ScheduleWithDelay_UsesDispatchDelayed( var resolver = new ModernDependencyResolver(); var builder = new ReactiveUIBuilder(resolver, resolver); var dispatcher = new MockDispatcher(); - builder.WithMauiScheduler(dispatcher); + _ = builder.WithMauiScheduler(dispatcher); var scheduler = builder.MainThreadScheduler!; var executed = false; - scheduler.Schedule(TimeSpan.FromMilliseconds(100), () => executed = true); + _ = scheduler.Schedule(TimeSpan.FromMilliseconds(100), () => executed = true); // Delays are routed through IDispatcher.DispatchDelayed (the dispatcher's native delayed dispatch), // not a created timer, and the forwarded delay is the remaining time until the due timestamp. @@ -152,8 +185,24 @@ public async Task MauiDispatcherSequencer_ScheduleWithDelay_UsesDispatchDelayed( await Assert.That(executed).IsTrue(); } + /// Temporarily overrides the mode detector so the code believes it is not running in a unit test. + /// A disposable that restores the default mode detector when disposed. + private static ActionDisposable ForceNonUnitTestMode() + { + ModeDetector.OverrideModeDetector(new AlwaysFalseModeDetector()); + return new ActionDisposable(static () => ModeDetector.OverrideModeDetector(new DefaultModeDetector())); + } + + /// Mode detector implementation that always reports it is not running in a unit test runner. + private sealed class AlwaysFalseModeDetector : IModeDetector + { + /// Indicates whether the code is running in a unit test runner. + /// Always returns . + public bool? InUnitTestRunner() => false; + } + /// Mock dispatcher that records immediate and delayed dispatch calls for testing. - private sealed class MockDispatcher : Microsoft.Maui.Dispatching.IDispatcher + private sealed class MockDispatcher : IDispatcher { /// Gets the number of times has been called. public int DispatchCount { get; private set; } @@ -180,7 +229,7 @@ public bool DispatchDelayed(TimeSpan delay, Action action) } /// - public Microsoft.Maui.Dispatching.IDispatcherTimer CreateTimer() => + public IDispatcherTimer CreateTimer() => throw new NotSupportedException("MauiDispatcherSequencer schedules delays via DispatchDelayed, not CreateTimer."); } } diff --git a/src/tests/ReactiveUI.Maui.Tests/MauiTestExecutor.cs b/src/tests/ReactiveUI.Maui.Tests/MauiTestExecutor.cs index 7cd14878f3..be9682731c 100644 --- a/src/tests/ReactiveUI.Maui.Tests/MauiTestExecutor.cs +++ b/src/tests/ReactiveUI.Maui.Tests/MauiTestExecutor.cs @@ -44,7 +44,7 @@ public virtual async ValueTask ExecuteTest(TestContext context, Func protected virtual void Initialize() { _previousProvider = DispatcherProvider.Current; - DispatcherProvider.SetCurrent(new TestDispatcherProvider()); + _ = DispatcherProvider.SetCurrent(new TestDispatcherProvider()); } /// Cleans up the MAUI test environment by restoring the previous dispatcher provider. @@ -55,6 +55,6 @@ protected virtual void CleanUp() return; } - DispatcherProvider.SetCurrent(_previousProvider); + _ = DispatcherProvider.SetCurrent(_previousProvider); } } diff --git a/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs b/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs index abb745ee1b..07866e86ca 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ReactiveShellContentTest.cs @@ -3,6 +3,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using Microsoft.Maui.Controls; +using ReactiveUI.Builder; +using ReactiveUI.Tests.Utilities.AppBuilder; +using Splat; +using TUnit.Core.Executors; + namespace ReactiveUI.Maui.Tests; /// Tests for . @@ -64,7 +70,57 @@ public async Task Contract_CanBeNull() await Assert.That(content.Contract).IsNull(); } + /// Setting the view model resolves the registered view and assigns the content template. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ViewModelChange_WithRegisteredView_SetsContentTemplate() + { + var content = new ReactiveShellContent { ViewModel = new() }; + + await Assert.That(content.ContentTemplate).IsNotNull(); + } + + /// Test executor that registers a view for the test view model. + [NotInParallel] + public sealed class ReactiveShellContentTestExecutor : MauiTestExecutor + { + /// The helper that configures and tears down the ReactiveUI app builder. + private readonly AppBuilderTestHelper _helper = new(); + + /// + protected override void Initialize() + { + base.Initialize(); + + _helper.Initialize(builder => _ = builder.WithMaui().WithCoreServices()); + + AppLocator.CurrentMutable.Register>(static () => new TestView()); + } + + /// + protected override void CleanUp() + { + _helper.CleanUp(); + base.CleanUp(); + } + } + /// Test view model for testing. [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "SST1436:Classes should not be empty", Justification = "Marker type for tests.")] private sealed class TestViewModel; + + /// Test view for the test view model. + private sealed class TestView : ContentView, IViewFor + { + /// + public TestViewModel? ViewModel { get; set; } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TestViewModel?)value; + } + } } diff --git a/src/tests/ReactiveUI.Maui.Tests/ReactiveTitleBarTests.cs b/src/tests/ReactiveUI.Maui.Tests/ReactiveTitleBarTests.cs new file mode 100644 index 0000000000..891a8aa7a3 --- /dev/null +++ b/src/tests/ReactiveUI.Maui.Tests/ReactiveTitleBarTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using TUnit.Core.Executors; + +namespace ReactiveUI.Maui.Tests; + +/// Tests for . +[TestExecutor] +public class ReactiveTitleBarTests +{ + /// Tests that the ViewModel bindable property is registered. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModelProperty_IsRegistered() => + await Assert.That(ReactiveTitleBar.ViewModelProperty).IsNotNull(); + + /// Tests that ViewModel property can be set and retrieved. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModel_SetAndGet_WorksCorrectly() + { + var titleBar = new ReactiveTitleBar(); + var viewModel = new TestViewModel { Name = "Test" }; + + titleBar.ViewModel = viewModel; + + using (Assert.Multiple()) + { + await Assert.That(titleBar.ViewModel).IsEqualTo(viewModel); + await Assert.That(titleBar.ViewModel?.Name).IsEqualTo("Test"); + } + } + + /// Tests that the IViewFor.ViewModel property works correctly. + /// A representing the asynchronous operation. + [Test] + public async Task IViewForViewModel_SetAndGet_WorksCorrectly() + { + var titleBar = new ReactiveTitleBar(); + var viewModel = new TestViewModel { Name = "Test" }; + + ((IViewFor)titleBar).ViewModel = viewModel; + + using (Assert.Multiple()) + { + await Assert.That(((IViewFor)titleBar).ViewModel).IsEqualTo(viewModel); + await Assert.That(titleBar.ViewModel).IsEqualTo(viewModel); + } + } + + /// Tests that setting the ViewModel updates the BindingContext. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModel_WhenSet_UpdatesBindingContext() + { + var titleBar = new ReactiveTitleBar(); + var viewModel = new TestViewModel { Name = "Test" }; + + titleBar.ViewModel = viewModel; + + await Assert.That(titleBar.BindingContext).IsEqualTo(viewModel); + } + + /// Tests that setting the BindingContext updates the ViewModel. + /// A representing the asynchronous operation. + [Test] + public async Task BindingContext_WhenSet_UpdatesViewModel() + { + var titleBar = new ReactiveTitleBar(); + var viewModel = new TestViewModel { Name = "Test" }; + + titleBar.BindingContext = viewModel; + + await Assert.That(titleBar.ViewModel).IsEqualTo(viewModel); + } + + /// Tests that the ViewModel can be set to null. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModel_SetToNull_WorksCorrectly() + { + var titleBar = new ReactiveTitleBar { ViewModel = new() { Name = "Test" } }; + + titleBar.ViewModel = null; + + using (Assert.Multiple()) + { + await Assert.That(titleBar.ViewModel).IsNull(); + await Assert.That(titleBar.BindingContext).IsNull(); + } + } + + /// Test view model. + private sealed class TestViewModel + { + /// Gets or sets the name. + public string? Name { get; set; } + } +} diff --git a/src/tests/ReactiveUI.Maui.Tests/ReactiveWindowTests.cs b/src/tests/ReactiveUI.Maui.Tests/ReactiveWindowTests.cs new file mode 100644 index 0000000000..eddd71226b --- /dev/null +++ b/src/tests/ReactiveUI.Maui.Tests/ReactiveWindowTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using TUnit.Core.Executors; + +namespace ReactiveUI.Maui.Tests; + +/// Tests for . +[TestExecutor] +public class ReactiveWindowTests +{ + /// Tests that the ViewModel bindable property is registered. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModelProperty_IsRegistered() => + await Assert.That(ReactiveWindow.ViewModelProperty).IsNotNull(); + + /// Tests that ViewModel property can be set and retrieved. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModel_SetAndGet_WorksCorrectly() + { + var window = new ReactiveWindow(); + var viewModel = new TestViewModel { Name = "Test" }; + + window.ViewModel = viewModel; + + using (Assert.Multiple()) + { + await Assert.That(window.ViewModel).IsEqualTo(viewModel); + await Assert.That(window.ViewModel?.Name).IsEqualTo("Test"); + } + } + + /// Tests that the IViewFor.ViewModel property works correctly. + /// A representing the asynchronous operation. + [Test] + public async Task IViewForViewModel_SetAndGet_WorksCorrectly() + { + var window = new ReactiveWindow(); + var viewModel = new TestViewModel { Name = "Test" }; + + ((IViewFor)window).ViewModel = viewModel; + + using (Assert.Multiple()) + { + await Assert.That(((IViewFor)window).ViewModel).IsEqualTo(viewModel); + await Assert.That(window.ViewModel).IsEqualTo(viewModel); + } + } + + /// Tests that setting the ViewModel updates the BindingContext. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModel_WhenSet_UpdatesBindingContext() + { + var window = new ReactiveWindow(); + var viewModel = new TestViewModel { Name = "Test" }; + + window.ViewModel = viewModel; + + await Assert.That(window.BindingContext).IsEqualTo(viewModel); + } + + /// Tests that setting the BindingContext updates the ViewModel. + /// A representing the asynchronous operation. + [Test] + public async Task BindingContext_WhenSet_UpdatesViewModel() + { + var window = new ReactiveWindow(); + var viewModel = new TestViewModel { Name = "Test" }; + + window.BindingContext = viewModel; + + await Assert.That(window.ViewModel).IsEqualTo(viewModel); + } + + /// Tests that the ViewModel can be set to null. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModel_SetToNull_WorksCorrectly() + { + var window = new ReactiveWindow { ViewModel = new() { Name = "Test" } }; + + window.ViewModel = null; + + using (Assert.Multiple()) + { + await Assert.That(window.ViewModel).IsNull(); + await Assert.That(window.BindingContext).IsNull(); + } + } + + /// Test view model. + private sealed class TestViewModel + { + /// Gets or sets the name. + public string? Name { get; set; } + } +} diff --git a/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostGenericTests.cs b/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostGenericTests.cs index 038253126d..e8d8af7009 100644 --- a/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostGenericTests.cs +++ b/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostGenericTests.cs @@ -220,7 +220,7 @@ protected override void Initialize() _helper.Initialize(builder => { - builder + _ = builder .WithMaui() .RegisterView() .WithCoreServices(); @@ -252,7 +252,7 @@ protected override void Initialize() _helper.Initialize(builder => { - builder + _ = builder .WithMaui() .WithCoreServices(); diff --git a/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs b/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs index 867a126981..e6ae8157e1 100644 --- a/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/RoutedViewHostTest.cs @@ -187,6 +187,169 @@ public async Task PageForViewModel_SetTitleOnNavigateTrue_SetsPageTitle() await Assert.That(page.Title).IsEqualTo(TestTitle); } + /// Invalidating the current view model is a safe no-op when there is no current view model or page. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task InvalidateCurrentViewModel_NoCurrentViewModelOrPage_ReturnsEarly() + { + var host = new TestableRoutedViewHost(); + + // The router has an empty navigation stack (no current view model) and there is no current page, + // so the call must short-circuit without throwing. + host.PublicInvalidateCurrentViewModel(); + + await Assert.That(host.Router?.GetCurrentViewModel()).IsNull(); + } + + /// The constructor throws when no is registered. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task Constructor_NoScreenRegistered_Throws() => + await Assert.That(static () => new RoutedViewHost()) + .Throws(); + + /// Invalidating with a matching current page view model type reassigns the current view model onto the page. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task InvalidateCurrentViewModel_MatchingViewModelType_UpdatesViewModel() + { + var host = new TestableRoutedViewHost(); + + // Make the current page an IViewFor whose view model is the same type as the router's current view model. + var routerViewModel = new TestRoutableViewModel(); + host.Router.NavigationStack.Add(routerViewModel); + + var pageViewModel = new TestRoutableViewModel(); + var page = new TestRoutableView { ViewModel = pageViewModel }; + await host.PushAsync(page); + + host.PublicInvalidateCurrentViewModel(); + + // The page's view model was replaced with the router's current view model (same type, so it is reassigned). + await Assert.That(page.ViewModel).IsEqualTo(routerViewModel); + } + + /// Invalidating with an incompatible current page view model type leaves the page view model untouched. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task InvalidateCurrentViewModel_IncompatibleViewModelType_DoesNotUpdate() + { + var host = new TestableRoutedViewHost(); + + // The router's current view model is a different type than the page's view model. + host.Router.NavigationStack.Add(new SecondRoutableViewModel()); + + var originalViewModel = new TestRoutableViewModel(); + var page = new TestRoutableView { ViewModel = originalViewModel }; + await host.PushAsync(page); + + host.PublicInvalidateCurrentViewModel(); + + // The types are incompatible, so the page's view model is not replaced. + await Assert.That(page.ViewModel).IsEqualTo(originalViewModel); + } + + /// Invalidating when the current page view model is takes the incompatible-type branch and does not assign. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task InvalidateCurrentViewModel_NullPageViewModel_DoesNotUpdate() + { + var host = new TestableRoutedViewHost(); + + // Push the page (whose view model is null) first so the navigation stack is non-empty before the + // router stack changes; otherwise the headless stack sync pushes into an empty navigation stack, + // which net10 MAUI rejects with an out-of-range List.Insert. + var page = new TestRoutableView(); + await host.PushAsync(page); + + host.Router.NavigationStack.Add(new TestRoutableViewModel()); + + host.PublicInvalidateCurrentViewModel(); + + await Assert.That(page.ViewModel).IsNull(); + } + + /// A navigate request whose stacks differ resolves the current page and pushes it via the navigate pipeline. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task Navigate_StacksDiffer_ResolvesAndPushesCurrentPage() + { + var host = new TestableRoutedViewHost(); + + // Pre-seed the navigation stack larger than the router stack so StacksAreDifferent enumerates the + // navigation stack without indexing past it once Navigate appends the new view model. + var seededView = CreateRoutableView(); + await host.PushAsync(seededView); + await host.PushAsync(CreateRoutableView()); + + host.Router.NavigationStack.Add(seededView.ViewModel!); + + // Navigate -> OnNavigateRequested -> StacksAreDifferent() == true -> schedules OnNavigateAsync -> + // ResolveCurrentPage resolves and pushes the current view model's page. + var navigateTarget = new TestRoutableViewModel(); + _ = host.Router.Navigate.Execute(navigateTarget).Subscribe(_ => { }); + await Task.Delay(SchedulerProcessingDelayMs); + + await Assert.That(host.Navigation.NavigationStack).IsNotEmpty(); + } + + /// A navigate request whose stacks already reference-match short-circuits in OnNavigateRequested. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task Navigate_StacksAlreadyMatch_ShortCircuits() + { + var host = new TestableRoutedViewHost(); + + // Make the pushed page reference-match the router view model that Navigate is about to append, so that the + // navigate pipeline observes StacksAreDifferent() == false and returns early. + var viewModel = new TestRoutableViewModel(); + var page = new TestRoutableView { ViewModel = viewModel }; + await host.PushAsync(page); + + var initialCount = host.Navigation.NavigationStack.Count; + + _ = host.Router.Navigate.Execute(viewModel).Subscribe(_ => { }); + await Task.Delay(SchedulerProcessingDelayMs); + + await Assert.That(host.Navigation.NavigationStack.Count).IsEqualTo(initialCount); + } + + /// Creates a routable view seeded with its own view model. + /// A new whose view model is set. + private static TestRoutableView CreateRoutableView() => + new() { ViewModel = new() }; + + /// Test executor that sets up MAUI environment without registering an . + [NotInParallel] + public sealed class MauiRoutedViewHostNoScreenExecutor : MauiTestExecutor + { + /// The helper that configures and tears down the ReactiveUI app builder. + private readonly AppBuilderTestHelper _helper = new(); + + /// + protected override void Initialize() + { + base.Initialize(); + + // Deliberately do not register an IScreen so the RoutedViewHost constructor throws. + _helper.Initialize(builder => _ = builder.WithMaui().WithCoreServices()); + } + + /// + protected override void CleanUp() + { + _helper.CleanUp(); + base.CleanUp(); + } + } + /// Test executor that sets up MAUI environment with view registration. [NotInParallel] public sealed class MauiRoutedViewHostTestExecutor : MauiTestExecutor @@ -201,7 +364,7 @@ protected override void Initialize() _helper.Initialize(builder => { - builder + _ = builder .WithMaui() .RegisterView() .WithCoreServices(); @@ -271,6 +434,16 @@ private sealed class TestRoutableView : ContentPage, IViewForA second routable view model of a distinct type, used to exercise incompatible-type branches. + private sealed class SecondRoutableViewModel : ReactiveObject, IRoutableViewModel + { + /// + public string? UrlPathSegment { get; set; } = "second"; + + /// + public IScreen HostScreen { get; } = null!; + } + /// Unregistered view model for testing error cases. private sealed class UnregisteredViewModel : ReactiveObject, IRoutableViewModel { diff --git a/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostGenericTests.cs b/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostGenericTests.cs index cd3b5b4c77..904094c140 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostGenericTests.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostGenericTests.cs @@ -182,10 +182,7 @@ public async Task IViewFor_ViewModel_CanBeSetToNull() public async Task IViewFor_ViewModelSetter_WorksCorrectly() { var viewModel = new TestViewModel(); - IViewFor host = new ViewModelViewHost(); - - host.ViewModel = viewModel; - + IViewFor host = new ViewModelViewHost { ViewModel = viewModel }; await Assert.That(host.ViewModel).IsSameReferenceAs(viewModel); await Assert.That(((ViewModelViewHost)host).ViewModel).IsSameReferenceAs(viewModel); } diff --git a/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs b/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs index 0693eb1bde..e205eb30ac 100644 --- a/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs +++ b/src/tests/ReactiveUI.Maui.Tests/ViewModelViewHostTest.cs @@ -4,7 +4,11 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics.CodeAnalysis; +using ReactiveUI.Builder; using ReactiveUI.Primitives.Signals; +using ReactiveUI.Tests.Utilities.AppBuilder; +using Splat; +using TUnit.Core.Executors; namespace ReactiveUI.Maui.Tests; @@ -134,6 +138,137 @@ public async Task DefaultContent_IsShown_WhenViewModelIsNull() await Assert.That(host.Content).IsEqualTo(defaultContent); } + /// Resolving a view model with no registered view throws. + [Test] + public void ResolveViewForViewModel_NoViewFound_Throws() + { + var host = new TestableViewModelViewHost { ViewLocator = new TestViewLocator(), ViewModel = new TestViewModel() }; + + _ = Assert.Throws(host.SimulateViewModelChange); + } + + /// Resolving a view model to a non-View instance throws. + [Test] + public void ResolveViewForViewModel_NonViewInstance_Throws() + { + var host = new TestableViewModelViewHost { ViewLocator = new NonViewLocator(), ViewModel = new TestViewModel() }; + + _ = Assert.Throws(host.SimulateViewModelChange); + } + + /// When not in a unit test runner, the constructor wires the contract subscription and resolves the (null) view model to the default content. + /// A representing the asynchronous operation. + [Test] + [NotInParallel] + public async Task Constructor_NotInUnitTestRunner_WiresSubscriptionAndResolves() + { + using (ForceNonUnitTestMode()) + { + // With no view model the constructor's subscription resolves to DefaultContent without throwing. + var host = new ViewModelViewHost(); + + await Assert.That(host.ViewContractObservable).IsNotNull(); + } + } + + /// When not in a unit test runner, changing the view model re-resolves and sets the content. + /// A representing the asynchronous operation. + [Test] + [NotInParallel] + public async Task OnViewModelPropertyChanged_NotInUnitTestRunner_ResolvesView() + { + using (ForceNonUnitTestMode()) + { + var view = new TestView(); + var host = new ViewModelViewHost { ViewLocator = new MockViewLocator(view) }; + + var viewModel = new TestViewModel(); + + // Setting ViewModel triggers OnViewModelPropertyChanged, which (outside a unit test runner) resolves the view. + host.ViewModel = viewModel; + + using (Assert.Multiple()) + { + await Assert.That(host.Content).IsEqualTo(view); + await Assert.That(view.ViewModel).IsEqualTo(viewModel); + } + } + } + + /// Resolving with a falls back to the ambient . + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task ResolveViewForViewModel_NullViewLocator_UsesCurrent() + { + var host = new TestableViewModelViewHost { ViewModel = new RegisteredViewModel() }; + + host.SimulateViewModelChange(); + + await Assert.That(host.Content).IsAssignableTo(); + } + + /// Temporarily overrides the mode detector so the code believes it is not running in a unit test. + /// A disposable that restores the previous mode detector when disposed. + private static ActionDisposable ForceNonUnitTestMode() + { + ModeDetector.OverrideModeDetector(new AlwaysFalseModeDetector()); + return new ActionDisposable(static () => ModeDetector.OverrideModeDetector(new DefaultModeDetector())); + } + + /// Test executor that sets up the MAUI environment and registers a view in for the null-locator fallback test. + [NotInParallel] + public sealed class ViewModelViewHostViewLocatorExecutor : MauiTestExecutor + { + /// The helper that configures and tears down the ReactiveUI app builder. + private readonly AppBuilderTestHelper _helper = new(); + + /// + protected override void Initialize() + { + base.Initialize(); + + _helper.Initialize(builder => + { + _ = builder.WithMaui().WithCoreServices(); + AppLocator.CurrentMutable.Register>(static () => new RegisteredView()); + }); + } + + /// + protected override void CleanUp() + { + _helper.CleanUp(); + base.CleanUp(); + } + } + + /// Mode detector implementation that always reports it is not running in a unit test runner. + private sealed class AlwaysFalseModeDetector : IModeDetector + { + /// Indicates whether the code is running in a unit test runner. + /// Always returns . + public bool? InUnitTestRunner() => false; + } + + /// A view model that is registered in for the fallback test. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "SST1436:Classes should not be empty", Justification = "Marker type for tests.")] + private sealed class RegisteredViewModel; + + /// The view resolved for via . + private sealed class RegisteredView : ContentView, IViewFor + { + /// + public RegisteredViewModel? ViewModel { get; set; } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (RegisteredViewModel?)value; + } + } + /// Test view model for testing. [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "SST1436:Classes should not be empty", Justification = "Marker type for tests.")] private sealed class TestViewModel; @@ -229,4 +364,53 @@ private sealed class TestViewLocator : IViewLocator "or generic constraints), trimming can't validate that the requirements of those annotations are met.")] public IViewFor? ResolveView(object? instance) => null; } + + /// A view that implements but is not a MAUI . + private sealed class NonView : IViewFor + { + /// + public TestViewModel? ViewModel { get; set; } + + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TestViewModel?)value; + } + } + + /// A view locator that resolves to a non-View instance. + [SuppressMessage( + "Major Code Smell", + "S4018:Generic methods should provide type parameters", + Justification = "IViewLocator declares parameterless generic ResolveView overloads that this mock must implement.")] + private sealed class NonViewLocator : IViewLocator + { + /// The non-View instance to resolve to. + private readonly NonView _view = new(); + + /// + public IViewFor? ResolveView(string? contract) + where T : class => _view as IViewFor; + + /// + public IViewFor? ResolveView() + where T : class => _view as IViewFor; + + /// + [RequiresUnreferencedCode( + "This method uses reflection to determine the view model type at runtime, which may be incompatible with trimming.")] + [RequiresDynamicCode( + "If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, " + + "or generic constraints), trimming can't validate that the requirements of those annotations are met.")] + public IViewFor? ResolveView(object? instance, string? contract) => _view; + + /// + [RequiresUnreferencedCode( + "This method uses reflection to determine the view model type at runtime, which may be incompatible with trimming.")] + [RequiresDynamicCode( + "If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, " + + "or generic constraints), trimming can't validate that the requirements of those annotations are met.")] + public IViewFor? ResolveView(object? instance) => _view; + } } diff --git a/src/tests/ReactiveUI.Routing.Tests/WhenAny/WhenAnyObservableChangeSetTests.cs b/src/tests/ReactiveUI.Routing.Tests/WhenAny/WhenAnyObservableChangeSetTests.cs index 874eb48138..6f19ffb8a4 100644 --- a/src/tests/ReactiveUI.Routing.Tests/WhenAny/WhenAnyObservableChangeSetTests.cs +++ b/src/tests/ReactiveUI.Routing.Tests/WhenAny/WhenAnyObservableChangeSetTests.cs @@ -19,7 +19,7 @@ public class WhenAnyObservableChangeSetTests public async Task WhenAnyObservableWithNullObjectShouldUpdateWhenObjectIsntNullAnymore() { var fixture = new ChangeSetWhenAnyViewModel(); - fixture.WhenAnyObservable(static x => x.Changes).Bind(out var output).ObserveOn(ImmediateScheduler.Instance).Subscribe(); + _ = fixture.WhenAnyObservable(static x => x.Changes).Bind(out var output).ObserveOn(ImmediateScheduler.Instance).Subscribe(); await Assert.That(output).IsEmpty(); fixture.MyListOfInts = []; @@ -48,7 +48,7 @@ public ObservableCollection? MyListOfInts get; set { - this.RaiseAndSetIfChanged(ref field, value); + _ = this.RaiseAndSetIfChanged(ref field, value); Changes = MyListOfInts?.ToObservableChangeSet(); } } diff --git a/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs b/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs index f5422a2159..0f96bc5b82 100644 --- a/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs +++ b/src/tests/ReactiveUI.Splat.Tests/SplatAdapterTests.cs @@ -27,7 +27,7 @@ public async Task DryIocDependencyResolver_Should_Register_ReactiveUI_BindingTyp // Invoke RxApp which initializes the ReactiveUI platform. var container = new Container(); container.UseDryIocDependencyResolver(); - Locator.CurrentMutable.CreateReactiveUIBuilder() + _ = Locator.CurrentMutable.CreateReactiveUIBuilder() .WithCoreServices() .Build(); @@ -49,7 +49,7 @@ public async Task DryIocDependencyResolver_Should_Register_ReactiveUI_CreatesCom // Invoke RxApp which initializes the ReactiveUI platform. var container = new Container(); container.UseDryIocDependencyResolver(); - Locator.CurrentMutable.CreateReactiveUIBuilder() + _ = Locator.CurrentMutable.CreateReactiveUIBuilder() .WithCoreServices() .Build(); @@ -74,7 +74,7 @@ public async Task AutofacDependencyResolver_Should_Register_ReactiveUI_BindingTy // Invoke RxApp which initializes the ReactiveUI platform. var builder = new ContainerBuilder(); var locator = new AutofacDependencyResolver(builder); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithCoreServices() .Build(); var container = builder.Build(); @@ -97,7 +97,7 @@ public async Task AutofacDependencyResolver_Should_Register_ReactiveUI_CreatesCo // Invoke RxApp which initializes the ReactiveUI platform. var builder = new ContainerBuilder(); var locator = new AutofacDependencyResolver(builder); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithCoreServices() .Build(); Locator.SetLocator(locator); @@ -124,7 +124,7 @@ public async Task NinjectDependencyResolver_Should_Register_ReactiveUI_BindingTy // Invoke RxApp which initializes the ReactiveUI platform. var container = new StandardKernel(); container.UseNinjectDependencyResolver(); - Locator.CurrentMutable.CreateReactiveUIBuilder() + _ = Locator.CurrentMutable.CreateReactiveUIBuilder() .WithCoreServices() .Build(); @@ -146,7 +146,7 @@ public async Task NinjectDependencyResolver_Should_Register_ReactiveUI_CreatesCo // Invoke RxApp which initializes the ReactiveUI platform. var container = new StandardKernel(); container.UseNinjectDependencyResolver(); - Locator.CurrentMutable.CreateReactiveUIBuilder() + _ = Locator.CurrentMutable.CreateReactiveUIBuilder() .WithCoreServices() .Build(); diff --git a/src/tests/ReactiveUI.Test.Utilities/AppBuilder/BaseAppBuilderTestExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/AppBuilder/BaseAppBuilderTestExecutor.cs index 364759501e..e9e66605ee 100644 --- a/src/tests/ReactiveUI.Test.Utilities/AppBuilder/BaseAppBuilderTestExecutor.cs +++ b/src/tests/ReactiveUI.Test.Utilities/AppBuilder/BaseAppBuilderTestExecutor.cs @@ -64,6 +64,6 @@ protected virtual void ConfigureAppBuilder(IReactiveUIBuilder builder, TestConte ArgumentNullException.ThrowIfNull(context); // Default implementation: just register core services - builder.WithCoreServices(); + _ = builder.WithCoreServices(); } } diff --git a/src/tests/ReactiveUI.Test.Utilities/Combined/WithSchedulerAndMessageBusExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Combined/WithSchedulerAndMessageBusExecutor.cs index 0c5613f17c..da7db43f08 100644 --- a/src/tests/ReactiveUI.Test.Utilities/Combined/WithSchedulerAndMessageBusExecutor.cs +++ b/src/tests/ReactiveUI.Test.Utilities/Combined/WithSchedulerAndMessageBusExecutor.cs @@ -57,7 +57,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont context.RestoreExecutionContext(); // Configure builder with schedulers, message bus, and core services - builder + _ = builder .WithMainThreadScheduler(scheduler) .WithTaskPoolScheduler(scheduler) .WithMessageBus(messageBus) diff --git a/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutor.cs index 161c244de0..407bf80d13 100644 --- a/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutor.cs +++ b/src/tests/ReactiveUI.Test.Utilities/Logging/LoggingRegistrationExecutor.cs @@ -43,7 +43,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont context.StateBag["TestLogManager"] = currentLogManager; // Configure builder with logging registration and core services - builder + _ = builder .WithRegistration(r => r.Register(() => currentLogManager)) .WithCoreServices(); } diff --git a/src/tests/ReactiveUI.Test.Utilities/MessageBus/WithMessageBusExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/MessageBus/WithMessageBusExecutor.cs index 75b135fc51..97fec24dda 100644 --- a/src/tests/ReactiveUI.Test.Utilities/MessageBus/WithMessageBusExecutor.cs +++ b/src/tests/ReactiveUI.Test.Utilities/MessageBus/WithMessageBusExecutor.cs @@ -60,7 +60,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont context.RestoreExecutionContext(); // Configure builder with schedulers, message bus, and core services - builder + _ = builder .WithMainThreadScheduler(scheduler) .WithTaskPoolScheduler(scheduler) .WithMessageBus(testBus) diff --git a/src/tests/ReactiveUI.Test.Utilities/ObservableTestCollectorExtensions.cs b/src/tests/ReactiveUI.Test.Utilities/ObservableTestCollectorExtensions.cs index 62d80fb018..18a8311cee 100644 --- a/src/tests/ReactiveUI.Test.Utilities/ObservableTestCollectorExtensions.cs +++ b/src/tests/ReactiveUI.Test.Utilities/ObservableTestCollectorExtensions.cs @@ -27,7 +27,7 @@ public Collection Collect() ArgumentExceptionHelper.ThrowIfNull(source); var items = new Collection(); - source.Subscribe(new CollectingObserver(items)); + _ = source.Subscribe(new CollectingObserver(items)); return items; } } diff --git a/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithSchedulerExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithSchedulerExecutor.cs index 0671579152..3a57e64144 100644 --- a/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithSchedulerExecutor.cs +++ b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithSchedulerExecutor.cs @@ -33,7 +33,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont context.StateBag.Items["Scheduler"] = scheduler; // Configure builder with schedulers and core services - builder + _ = builder .WithMainThreadScheduler(scheduler) .WithTaskPoolScheduler(scheduler) .WithCoreServices(); diff --git a/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithVirtualTimeSchedulerExecutor.cs b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithVirtualTimeSchedulerExecutor.cs index 88f9740098..e12ed59e40 100644 --- a/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithVirtualTimeSchedulerExecutor.cs +++ b/src/tests/ReactiveUI.Test.Utilities/Schedulers/WithVirtualTimeSchedulerExecutor.cs @@ -34,7 +34,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont context.StateBag.Items["Scheduler"] = scheduler; // Configure builder with virtual time scheduler and core services - builder + _ = builder .WithMainThreadScheduler(scheduler) .WithTaskPoolScheduler(scheduler) .WithCoreServices(); diff --git a/src/tests/ReactiveUI.Testing.Tests/MessageBusExtensionsTests.cs b/src/tests/ReactiveUI.Testing.Tests/MessageBusExtensionsTests.cs index 33b90ab265..d5a052b230 100644 --- a/src/tests/ReactiveUI.Testing.Tests/MessageBusExtensionsTests.cs +++ b/src/tests/ReactiveUI.Testing.Tests/MessageBusExtensionsTests.cs @@ -47,7 +47,7 @@ public async Task WithMessageBus_AllowsMessageBusOperations() using (testBus.WithMessageBus()) { - MessageBus.Current.Listen().Subscribe(Witness.Create(_ => messageReceived = true)); + _ = MessageBus.Current.Listen().Subscribe(Witness.Create(_ => messageReceived = true)); // Act MessageBus.Current.SendMessage("test"); @@ -145,7 +145,7 @@ public async Task With_Function_RestoresOriginalMessageBus() var testBus = new MessageBus(); // Act - testBus.With(() => ExpectedFunctionResult); + _ = testBus.With(() => ExpectedFunctionResult); // Assert await Assert.That(MessageBus.Current).IsSameReferenceAs(originalBus); diff --git a/src/tests/ReactiveUI.Testing.Tests/TestSequencerTests.cs b/src/tests/ReactiveUI.Testing.Tests/TestSequencerTests.cs index 6ea99c7277..a59f2a745d 100644 --- a/src/tests/ReactiveUI.Testing.Tests/TestSequencerTests.cs +++ b/src/tests/ReactiveUI.Testing.Tests/TestSequencerTests.cs @@ -34,7 +34,7 @@ public async Task Should_Execute_Tests_In_Order() var tcs = new TaskCompletionSource(); var advanceCount = 0; - subject.Skip(1).Subscribe(Witness.Create(ignored => _ = AdvancePhaseAsync())); + _ = subject.Skip(1).Subscribe(Witness.Create(ignored => _ = AdvancePhaseAsync())); using (Assert.Multiple()) { @@ -89,12 +89,12 @@ async Task AdvancePhaseAsync() try { await testSequencer.AdvancePhaseAsync(); - Interlocked.Increment(ref advanceCount); - tcs.TrySetResult(true); + _ = Interlocked.Increment(ref advanceCount); + _ = tcs.TrySetResult(true); } catch (Exception ex) { - tcs.TrySetException(ex); + _ = tcs.TrySetException(ex); } } } diff --git a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs index 0c8a2a4a8a..ba6ff7fb41 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewModelTests.cs @@ -16,10 +16,10 @@ public async Task ActivationsGetRefCounted() var fixture = new ActivatingViewModel(); await Assert.That(fixture.IsActiveCount).IsEqualTo(0); - fixture.Activator.Activate(); + _ = fixture.Activator.Activate(); await Assert.That(fixture.IsActiveCount).IsEqualTo(1); - fixture.Activator.Activate(); + _ = fixture.Activator.Activate(); await Assert.That(fixture.IsActiveCount).IsEqualTo(1); fixture.Activator.Deactivate(); @@ -42,14 +42,14 @@ public async Task DerivedActivationsDontGetStomped() await Assert.That(fixture.IsActiveCountAlso).IsEqualTo(0); } - fixture.Activator.Activate(); + _ = fixture.Activator.Activate(); using (Assert.Multiple()) { await Assert.That(fixture.IsActiveCount).IsEqualTo(1); await Assert.That(fixture.IsActiveCountAlso).IsEqualTo(1); } - fixture.Activator.Activate(); + _ = fixture.Activator.Activate(); using (Assert.Multiple()) { await Assert.That(fixture.IsActiveCount).IsEqualTo(1); diff --git a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs index 5150e64744..d4681a14bb 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ActivatingViewTests.cs @@ -55,7 +55,7 @@ public async Task ActivatingViewSmokeTest() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithCoreServices() .WithCustomRegistration(builder => builder.Register(() => new ActivatingViewFetcher())).BuildApp(); @@ -93,7 +93,7 @@ public async Task CanUnloadAndLoadViewAgain() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithCoreServices() .WithCustomRegistration(builder => builder.Register(() => new ActivatingViewFetcher())).BuildApp(); @@ -138,7 +138,7 @@ public async Task NullingViewModelDeactivateIt() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithCoreServices() .WithCustomRegistration(builder => builder.Register(() => new ActivatingViewFetcher())).BuildApp(); @@ -172,7 +172,7 @@ public async Task SettingViewModelAfterLoadedLoadsIt() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithCoreServices() .WithCustomRegistration(builder => builder.Register(() => new ActivatingViewFetcher())).BuildApp(); @@ -214,7 +214,7 @@ public async Task SwitchingViewModelDeactivatesIt() { AppBuilder.ResetBuilderStateForTests(); var locator = new ModernDependencyResolver(); - locator.CreateReactiveUIBuilder() + _ = locator.CreateReactiveUIBuilder() .WithCoreServices() .WithCustomRegistration(builder => builder.Register(() => new ActivatingViewFetcher())).BuildApp(); diff --git a/src/tests/ReactiveUI.Tests/Activation/ViewForMixinsTests.cs b/src/tests/ReactiveUI.Tests/Activation/ViewForMixinsTests.cs new file mode 100644 index 0000000000..15e1b2ffcb --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Activation/ViewForMixinsTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +#if REACTIVE_SHIM +using ReactiveUI.Reactive.Builder; +#else +using ReactiveUI.Builder; +#endif +using Splat; +using Splat.Builder; + +namespace ReactiveUI.Tests.Activation; + +/// Tests for the activation extension members on . +public class ViewForMixinsTests +{ + /// The Func<IEnumerable<IDisposable>> activation overload runs the block on activation and + /// disposes its returned resources on deactivation. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModelWhenActivatedFuncOverloadActivatesAndDeactivates() + { + var activations = 0; + var deactivations = 0; + var viewModel = new ActivatableViewModelMock(); + + viewModel.WhenActivated(() => + { + activations++; + return [Scope.Create(() => deactivations++)]; + }); + + using (Assert.Multiple()) + { + await Assert.That(activations).IsEqualTo(0); + await Assert.That(deactivations).IsEqualTo(0); + } + + _ = viewModel.Activator.Activate(); + await Assert.That(activations).IsEqualTo(1); + + viewModel.Activator.Deactivate(); + await Assert.That(deactivations).IsEqualTo(1); + } + + /// The parameterless WhenActivated() overload activates the view through its registered fetcher. + /// A representing the asynchronous operation. + [Test] + public async Task ViewWhenActivatedNoArgOverloadActivatesViewModel() + { + AppBuilder.ResetBuilderStateForTests(); + var locator = new ModernDependencyResolver(); + _ = locator.CreateReactiveUIBuilder() + .WithCoreServices() + .WithCustomRegistration(builder => + builder.Register(() => new ActivatingViewFetcher())).BuildApp(); + + using (locator.WithResolver()) + { + var viewModel = new ActivatingViewModel(); + var fixture = new ActivatingView { ViewModel = viewModel }; + + using var activation = fixture.WhenActivated(); + + fixture.Loaded.OnNext(RxVoid.Default); + await Assert.That(viewModel.IsActiveCount).IsEqualTo(1); + + fixture.Unloaded.OnNext(RxVoid.Default); + await Assert.That(viewModel.IsActiveCount).IsEqualTo(0); + } + } + + /// A minimal activatable view model used to drive the activation lifecycle. + private sealed class ActivatableViewModelMock : ReactiveObject, IActivatableViewModel + { + /// + public ViewModelActivator Activator { get; } = new(); + } +} diff --git a/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs b/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs index 64de55f2ea..31cde31ec8 100644 --- a/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs +++ b/src/tests/ReactiveUI.Tests/Activation/ViewModelActivatorTests.cs @@ -16,7 +16,7 @@ public async Task TestActivatingTicksActivatedObservable() var viewModelActivator = new ViewModelActivator(); var activated = viewModelActivator.Activated.Collect(); - viewModelActivator.Activate(); + _ = viewModelActivator.Activate(); await Assert.That(activated).Count().IsEqualTo(1); } @@ -42,7 +42,7 @@ public async Task TestDeactivatingFollowingActivatingTicksDeactivatedObservable( var viewModelActivator = new ViewModelActivator(); var deactivated = viewModelActivator.Deactivated.Collect(); - viewModelActivator.Activate(); + _ = viewModelActivator.Activate(); viewModelActivator.Deactivate(); await Assert.That(deactivated).Count().IsEqualTo(1); @@ -71,12 +71,10 @@ public async Task TestDisposingAfterActivationDeactivatesViewModel() var deactivated = viewModelActivator.Deactivated.Collect(); using (viewModelActivator.Activate()) + using (Assert.Multiple()) { - using (Assert.Multiple()) - { - await Assert.That(activated).Count().IsEqualTo(1); - await Assert.That(deactivated).IsEmpty(); - } + await Assert.That(activated).Count().IsEqualTo(1); + await Assert.That(deactivated).IsEmpty(); } using (Assert.Multiple()) diff --git a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTest.cs b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTest.cs index 5d09c499c7..e61af380b2 100644 --- a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTest.cs +++ b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistCollectionTest.cs @@ -104,7 +104,7 @@ public async Task AutoPersistCollection_DuplicateAdd_NoDoubleSave() var fixture = new ObservableCollection(); var timesSaved = 0; - fixture.AutoPersistCollection( + _ = fixture.AutoPersistCollection( _ => { timesSaved++; @@ -142,7 +142,7 @@ public async Task AutoPersistCollection_Lifecycle_ManagesPersistence() var fixture = new ResettableCollection { item }; var timesSaved = 0; - fixture.AutoPersistCollection( + _ = fixture.AutoPersistCollection( _ => { timesSaved++; @@ -201,7 +201,7 @@ public async Task AutoPersistCollection_ManualSave_TriggersImmediateSave() var fixture = new ResettableCollection { item }; var timesSaved = 0; - fixture.AutoPersistCollection( + _ = fixture.AutoPersistCollection( _ => { timesSaved++; @@ -234,7 +234,7 @@ public async Task AutoPersistCollection_MetadataProvider_WorksCorrectly() var metadataProvider = AutoPersistHelperMixins.CreateMetadataProvider(); var timesSaved = 0; - fixture.AutoPersistCollection( + _ = fixture.AutoPersistCollection( _ => { timesSaved++; @@ -267,7 +267,7 @@ public async Task AutoPersistCollection_Reset_ContinuesPersistence() var fixture = new ResettableCollection { item }; var timesSaved = 0; - fixture.AutoPersistCollection( + _ = fixture.AutoPersistCollection( _ => { timesSaved++; diff --git a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistHelperTest.cs b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistHelperTest.cs index d93add0e73..d4fef92099 100644 --- a/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistHelperTest.cs +++ b/src/tests/ReactiveUI.Tests/AutoPersist/AutoPersistHelperTest.cs @@ -3,8 +3,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Collections; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; using ReactiveUI.Tests.ReactiveObjects.Mocks; using ReactiveUI.Tests.Utilities.Schedulers; using TUnit.Core.Executors; @@ -28,6 +31,9 @@ public class AutoPersistHelperTest /// Milliseconds to advance past the throttle interval to allow a save to fire. private const int PastThrottleMilliseconds = 150; + /// The default debounce interval, in seconds, used by the reflection-based overloads. + private const int DefaultIntervalSeconds = 3; + /// Tests that ActOnEveryObject calls onAdd when new item added. /// A representing the asynchronous unit test. [Test] @@ -36,7 +42,7 @@ public async Task ActOnEveryObject_AddNewItem_CallsOnAdd() var collection = new ObservableCollection(); var addedItems = new List(); - collection.ActOnEveryObject( + _ = collection.ActOnEveryObject( addedItems.Add, _ => { }); @@ -61,7 +67,7 @@ public async Task ActOnEveryObject_ClearCollection_CallsOnRemove() var collection = new ObservableCollection { item1, item2 }; var removedItems = new List(); - collection.ActOnEveryObject( + _ = collection.ActOnEveryObject( _ => { }, removedItems.Add); @@ -111,7 +117,7 @@ public async Task ActOnEveryObject_ExistingItems_CallsOnAdd() var collection = new ObservableCollection { item1, item2 }; var addedItems = new List(); - collection.ActOnEveryObject( + _ = collection.ActOnEveryObject( addedItems.Add, _ => { }); @@ -132,7 +138,7 @@ public async Task ActOnEveryObject_NullCollection_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - collection!.ActOnEveryObject(_ => { }, _ => { }); + _ = collection!.ActOnEveryObject(_ => { }, _ => { }); await Task.CompletedTask; }); } @@ -146,7 +152,7 @@ public async Task ActOnEveryObject_NullOnAdd_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - collection.ActOnEveryObject(null!, _ => { }); + _ = collection.ActOnEveryObject(null!, _ => { }); await Task.CompletedTask; }); } @@ -160,7 +166,7 @@ public async Task ActOnEveryObject_NullOnRemove_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - collection.ActOnEveryObject(_ => { }, null!); + _ = collection.ActOnEveryObject(_ => { }, null!); await Task.CompletedTask; }); } @@ -174,7 +180,7 @@ public async Task ActOnEveryObject_ReadOnlyCollection_WorksCorrectly() var readOnlyCollection = new ReadOnlyObservableCollection(innerCollection); var addedItems = new List(); - readOnlyCollection.ActOnEveryObject( + _ = readOnlyCollection.ActOnEveryObject( addedItems.Add, _ => { }); @@ -197,11 +203,11 @@ public async Task ActOnEveryObject_RemoveItem_CallsOnRemove() var collection = new ObservableCollection { item }; var removedItems = new List(); - collection.ActOnEveryObject( + _ = collection.ActOnEveryObject( _ => { }, removedItems.Add); - collection.Remove(item); + _ = collection.Remove(item); using (Assert.Multiple()) { @@ -222,7 +228,7 @@ public async Task ActOnEveryObject_ReplaceItem_CallsOnRemoveAndOnAdd() var addedItems = new List(); var removedItems = new List(); - collection.ActOnEveryObject( + _ = collection.ActOnEveryObject( addedItems.Add, removedItems.Add); @@ -284,7 +290,7 @@ public async Task AutoPersist_ManualSaveSignal_TriggersSave() var manualSave = new Signal(); var saveCount = 0; - fixture.AutoPersist( + _ = fixture.AutoPersist( _ => { saveCount++; @@ -325,7 +331,7 @@ public async Task AutoPersist_NoDataContract_ThrowsArgumentException() await Assert.ThrowsAsync(async () => { - obj.AutoPersist(_ => SingleValueObservable.Void); + _ = obj.AutoPersist(_ => SingleValueObservable.Void); await Task.CompletedTask; }); } @@ -341,7 +347,7 @@ public async Task AutoPersist_NonDataMemberProperty_DoesNotTriggerSave() var fixture = new TestFixture(); var saveCount = 0; - fixture.AutoPersist( + _ = fixture.AutoPersist( _ => { saveCount++; @@ -367,7 +373,7 @@ public async Task AutoPersist_NullDoPersist_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - fixture.AutoPersist(null!); + _ = fixture.AutoPersist(null!); await Task.CompletedTask; }); } @@ -381,7 +387,7 @@ public async Task AutoPersist_NullManualSaveSignal_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - fixture.AutoPersist(_ => SingleValueObservable.Void, null!, TimeSpan.FromSeconds(1)); + _ = fixture.AutoPersist(_ => SingleValueObservable.Void, null!, TimeSpan.FromSeconds(1)); await Task.CompletedTask; }); } @@ -399,7 +405,7 @@ public async Task AutoPersist_NullMetadata_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - fixture.AutoPersist(_ => SingleValueObservable.Void, null!, TimeSpan.FromSeconds(1)); + _ = fixture.AutoPersist(_ => SingleValueObservable.Void, null!, TimeSpan.FromSeconds(1)); await Task.CompletedTask; }); } @@ -415,7 +421,7 @@ public async Task AutoPersist_PropertyChange_TriggersSave() var fixture = new TestFixture(); var saveCount = 0; - fixture.AutoPersist( + _ = fixture.AutoPersist( _ => { saveCount++; @@ -442,7 +448,7 @@ public async Task AutoPersist_Throttle_RespectInterval() var fixture = new TestFixture(); var saveCount = 0; - fixture.AutoPersist( + _ = fixture.AutoPersist( _ => { saveCount++; @@ -476,7 +482,7 @@ public async Task AutoPersist_WithMetadata_SavesCorrectly() var metadata = AutoPersistHelperMixins.CreateMetadata(); var saveCount = 0; - fixture.AutoPersist( + _ = fixture.AutoPersist( _ => { saveCount++; @@ -503,7 +509,7 @@ public async Task AutoPersistCollection_AddItem_EnablesPersistence() var collection = new ObservableCollection(); var saveCount = 0; - collection.AutoPersistCollection( + _ = collection.AutoPersistCollection( _ => { saveCount++; @@ -532,7 +538,7 @@ public async Task AutoPersistCollection_NullCollection_ThrowsArgumentNullExcepti await Assert.ThrowsAsync(async () => { - collection!.AutoPersistCollection(_ => SingleValueObservable.Void); + _ = collection!.AutoPersistCollection(_ => SingleValueObservable.Void); await Task.CompletedTask; }); } @@ -546,7 +552,7 @@ public async Task AutoPersistCollection_NullDoPersist_ThrowsArgumentNullExceptio await Assert.ThrowsAsync(async () => { - collection.AutoPersistCollection(null!); + _ = collection.AutoPersistCollection(null!); await Task.CompletedTask; }); } @@ -560,7 +566,7 @@ public async Task AutoPersistCollection_NullMetadata_ThrowsArgumentNullException await Assert.ThrowsAsync(async () => { - collection.AutoPersistCollection(_ => SingleValueObservable.Void, (AutoPersistHelperMixins.AutoPersistMetadata)null!); + _ = collection.AutoPersistCollection(_ => SingleValueObservable.Void, (AutoPersistHelperMixins.AutoPersistMetadata)null!); await Task.CompletedTask; }); } @@ -578,7 +584,7 @@ public async Task AutoPersistCollection_ReadOnlyCollection_WorksCorrectly() var saveCount = 0; var manualSave = new Signal(); - readOnlyCollection.AutoPersistCollection( + _ = readOnlyCollection.AutoPersistCollection( _ => { saveCount++; @@ -611,7 +617,7 @@ public async Task AutoPersistCollection_RemoveItem_DisablesPersistence() var collection = new ObservableCollection { item }; var saveCount = 0; - collection.AutoPersistCollection( + _ = collection.AutoPersistCollection( _ => { saveCount++; @@ -621,7 +627,7 @@ public async Task AutoPersistCollection_RemoveItem_DisablesPersistence() scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); - collection.Remove(item); + _ = collection.Remove(item); item.IsNotNullString = "Test"; scheduler.AdvanceBy(TimeSpan.FromMilliseconds(PastThrottleMilliseconds)); @@ -641,7 +647,7 @@ public async Task AutoPersistCollection_WithMetadata_SavesCorrectly() var metadata = AutoPersistHelperMixins.CreateMetadata(); var saveCount = 0; - collection.AutoPersistCollection( + _ = collection.AutoPersistCollection( _ => { saveCount++; @@ -705,6 +711,502 @@ public async Task CreateMetadata_CachesMetadata() await Assert.That(metadata1).IsSameReferenceAs(metadata2); } + /// + /// Tests the three-argument ObservableCollection metadata overload (no interval) so that the + /// forwarding entry point and the generic three-argument forwarder are both exercised. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_ManualSignalAndMetadataNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new ObservableCollection(); + var metadata = AutoPersistHelperMixins.CreateMetadata(); + var manualSave = new Signal(); + var saveCount = 0; + + _ = collection.AutoPersistCollection( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave, + metadata); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + manualSave.OnNext(RxVoid.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// Tests the two-argument reflection-based ObservableCollection overload (manual signal, no interval). + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_ManualSignalNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new ObservableCollection(); + var manualSave = new Signal(); + var saveCount = 0; + + _ = collection.AutoPersistCollection( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + manualSave.OnNext(RxVoid.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// Tests the three-argument read-only collection metadata overload and the metadata+interval body. + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_ReadOnlyManualSignalAndMetadataNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var innerCollection = new ObservableCollection(); + var readOnlyCollection = new ReadOnlyObservableCollection(innerCollection); + var metadata = AutoPersistHelperMixins.CreateMetadata(); + var manualSave = new Signal(); + var saveCount = 0; + + _ = readOnlyCollection.AutoPersistCollection( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave, + metadata); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + innerCollection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + manualSave.OnNext(RxVoid.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// Tests the two-argument reflection-based read-only collection overload (manual signal, no interval). + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_ReadOnlyManualSignalNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var innerCollection = new ObservableCollection(); + var readOnlyCollection = new ReadOnlyObservableCollection(innerCollection); + var manualSave = new Signal(); + var saveCount = 0; + + _ = readOnlyCollection.AutoPersistCollection( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + innerCollection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + manualSave.OnNext(RxVoid.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// Tests the generic collection metadata-provider overload without an explicit interval (three-argument form). + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_MetadataProviderNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new ObservableCollection(); + var metadataProvider = AutoPersistHelperMixins.CreateMetadataProvider(); + var manualSave = new Signal(); + var saveCount = 0; + + _ = collection.AutoPersistCollection( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave, + metadataProvider); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + item.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that disposing a metadata-provider collection subscription disposes the live per-item + /// persistence subscriptions, exercising the dispose loop over a non-empty disposer list. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_MetadataProviderDispose_StopsPersistence() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new ObservableCollection(); + var metadataProvider = AutoPersistHelperMixins.CreateMetadataProvider(); + var saveCount = 0; + + var subscription = collection.AutoPersistCollection( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + new Signal(), + metadataProvider, + TimeSpan.FromMilliseconds(ThrottleMilliseconds)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + subscription.Dispose(); + + item.IsNotNullString = "AfterDispose"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(PastThrottleMilliseconds)); + + await Assert.That(saveCount).IsEqualTo(0); + } + + /// + /// Tests that the generic collection metadata overload throws when the supplied metadata reports no + /// [DataContract], covering the HasDataContract guard. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task AutoPersistCollection_MetadataWithoutDataContract_ThrowsArgumentException() + { + var collection = new ObservableCollection(); + var metadata = new AutoPersistHelperMixins.AutoPersistMetadata(false, new HashSet()); + + await Assert.ThrowsAsync(async () => + { + _ = collection.AutoPersistCollection(_ => SingleValueObservable.Void, new Signal(), metadata, interval: null); + await Task.CompletedTask; + }); + } + + /// Tests the generic collection metadata overload (no interval) on a non-ObservableCollection collection type. + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_GenericMetadataNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new CustomNotifyCollection(); + var metadata = AutoPersistHelperMixins.CreateMetadata(); + var manualSave = new Signal(); + var saveCount = 0; + + _ = AutoPersistHelperMixins.AutoPersistCollection, RxVoid>( + collection, + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave, + metadata); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + manualSave.OnNext(RxVoid.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// Tests that disposing while a debounced save is pending cancels the save without persisting. + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_DisposeWithPendingSave_DoesNotPersist() + { + const int IntervalSeconds = 2; + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var saveCount = 0; + + var subscription = fixture.AutoPersist( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + TimeSpan.FromSeconds(IntervalSeconds)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + fixture.IsNotNullString = "Pending"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + subscription.Dispose(); + scheduler.AdvanceBy(TimeSpan.FromSeconds(IntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(0); + } + + /// Tests that changing a reactive property not marked [DataMember] does not trigger a save. + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_NonPersistableRaisedProperty_DoesNotTriggerSave() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var saveCount = 0; + + _ = fixture.AutoPersist( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + TimeSpan.FromSeconds(1)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + fixture.NotSerialized = "Changed"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + await Assert.That(saveCount).IsEqualTo(0); + } + + /// Tests the two-argument reactive-object AutoPersist overload (manual signal, no interval). + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_ManualSignalNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var fixture = new TestFixture(); + var manualSave = new Signal(); + var saveCount = 0; + + _ = fixture.AutoPersist( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + manualSave.OnNext(RxVoid.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// + /// Tests that AutoPersist reflects over the runtime type when it differs from the generic type argument, + /// exercising the unknown-runtime-type metadata lookup. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersist_DerivedRuntimeType_UsesRuntimeMetadata() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var derived = new DerivedDataContractFixture(); + DataContractBaseFixture fixture = derived; + var saveCount = 0; + + _ = fixture.AutoPersist( + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + TimeSpan.FromSeconds(1)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + derived.DerivedValue = "Changed"; + scheduler.AdvanceBy(TimeSpan.FromSeconds(1)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// Tests that a Refresh change set entry invokes onRemove then onAdd for the refreshed item. + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_RefreshChange_CallsRemoveThenAdd() + { + var item = new TestFixture(); + var changeSets = new Signal>(); + var adds = new List(); + var removes = new List(); + + using var subscription = changeSets.ActOnEveryObject(adds.Add, removes.Add); + + changeSets.OnNext(new ReactiveChangeSet( + [new ReactiveChange(ReactiveChangeReason.Refresh, item, default, 0, -1)])); + + using (Assert.Multiple()) + { + await Assert.That(removes).Contains(item); + await Assert.That(adds).Contains(item); + } + } + + /// Tests that a Replace change set entry with a non-null previous invokes onRemove for the previous and onAdd for the current. + /// A representing the asynchronous unit test. + [Test] + public async Task ActOnEveryObject_ReplaceChangeWithPrevious_CallsRemoveAndAdd() + { + var previous = new TestFixture(); + var current = new TestFixture(); + var changeSets = new Signal>(); + var adds = new List(); + var removes = new List(); + + using var subscription = changeSets.ActOnEveryObject(adds.Add, removes.Add); + + changeSets.OnNext(new ReactiveChangeSet( + [new ReactiveChange(ReactiveChangeReason.Replace, current, previous, 0, -1)])); + + using (Assert.Multiple()) + { + await Assert.That(removes).Contains(previous); + await Assert.That(adds).Contains(current); + } + } + + /// + /// Tests that AutoPersistCollection tolerates a Replace whose previous item was never tracked, exercising the + /// dispose-and-remove early-return for an item not present in the disposer list. + /// + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_ReplaceUntrackedPrevious_DisposeAndRemoveSkips() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var untracked = new TestFixture(); + var current = new TestFixture(); + var collection = new ReplaceableCollection(); + var saveCount = 0; + + _ = AutoPersistHelperMixins.AutoPersistCollection, RxVoid>( + collection, + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + new Signal(), + TimeSpan.FromMilliseconds(ThrottleMilliseconds)); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + // The Replace carries a previous item that AutoPersist never tracked (it was never added via a + // notification), taking the dispose-and-remove not-found early-return. + collection.RaiseReplace(untracked, current, 0); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + current.IsNotNullString = "Test"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(PastThrottleMilliseconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + + /// Tests the generic-collection reflection-based two-argument overload (manual signal, no interval) on a custom collection type. + /// A representing the asynchronous unit test. + [Test] + [TestExecutor] + public async Task AutoPersistCollection_GenericReflectionManualSignalNoInterval_SavesCorrectly() + { + var scheduler = TestContext.Current.GetVirtualTimeScheduler(); + + var collection = new CustomNotifyCollection(); + var manualSave = new Signal(); + var saveCount = 0; + + _ = AutoPersistHelperMixins.AutoPersistCollection, RxVoid>( + collection, + _ => + { + saveCount++; + return SingleValueObservable.Void; + }, + manualSave); + + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(InitialAdvanceMilliseconds)); + + var item = new TestFixture(); + collection.Add(item); + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); + + manualSave.OnNext(RxVoid.Default); + scheduler.AdvanceBy(TimeSpan.FromSeconds(DefaultIntervalSeconds)); + + await Assert.That(saveCount).IsEqualTo(1); + } + /// Test object without DataContract attribute. private sealed class ObjectWithoutDataContract : ReactiveObject { @@ -715,4 +1217,101 @@ public string? Property set => this.RaiseAndSetIfChanged(ref field, value); } } + + /// A [DataContract] base reactive type used to exercise runtime-type metadata resolution. + [DataContract] + private class DataContractBaseFixture : ReactiveObject + { + /// Gets or sets a persistable base value. + [DataMember] + public string? BaseValue + { + get; + set => this.RaiseAndSetIfChanged(ref field, value); + } + } + + /// A derived reactive type whose runtime type differs from the statically known base type. + [DataContract] + private sealed class DerivedDataContractFixture : DataContractBaseFixture + { + /// Gets or sets a persistable derived value. + [DataMember] + public string? DerivedValue + { + get; + set => this.RaiseAndSetIfChanged(ref field, value); + } + } + + /// A change-notifying collection that can raise a Replace with an arbitrary previous item. + /// The element type. + private sealed class ReplaceableCollection : IEnumerable, INotifyCollectionChanged + { + /// The backing storage for the collection items. + private readonly List _items = []; + + /// + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + /// Raises a Replace notification swapping a previous item for a current item. + /// The previous item. + /// The current item. + /// The index at which the replace occurs. + public void RaiseReplace(T previous, T current, int index) + { + if (index < _items.Count) + { + _items[index] = current; + } + else + { + _items.Add(current); + } + + CollectionChanged?.Invoke( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + current, + previous, + index)); + } + + /// + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + /// A change-notifying collection that is neither nor read-only, used to bind the generic AutoPersist extension block. + /// The element type. + private sealed class CustomNotifyCollection : IEnumerable, INotifyCollectionChanged + { + /// The backing storage for the collection items. + private readonly List _items = []; + + /// + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + /// Adds an item to the collection and raises a collection-changed notification. + /// The item to add. + public void Add(T item) + { + _items.Add(item); + CollectionChanged?.Invoke( + this, + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + item, + _items.Count - 1)); + } + + /// + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } } diff --git a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs index 75085f81eb..a6569b2ee2 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaCommandParameterTests.cs @@ -130,7 +130,7 @@ public void BindCommandToObject_WithNullTarget_Throws() var binder = new CreatesCommandBindingViaCommandParameter(); var command = ReactiveCommand.Create(() => { }); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject(command, null, Signal.Emit(null))); } diff --git a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs index 9c8c3a4340..b3507a81bf 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/CommandBindings/CreatesCommandBindingViaEventTests.cs @@ -148,7 +148,7 @@ public void BindCommandToObject_WithNoEvents_Throws() var target = new object(); var command = ReactiveCommand.Create(() => { }, outputScheduler: Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject(command, target, Signal.Emit(null))); } @@ -159,7 +159,7 @@ public void BindCommandToObject_WithNullTarget_Throws() var binder = new CreatesCommandBindingViaEvent(); var command = ReactiveCommand.Create(() => { }, outputScheduler: Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject(command, null, Signal.Emit(null))); } @@ -290,7 +290,7 @@ public void BindCommandToObject_WithNullEventName_Throws() var target = new ClickableControl(); var command = ReactiveCommand.Create(() => { }, outputScheduler: Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject( command, target, @@ -306,7 +306,7 @@ public void BindCommandToObject_WithEmptyEventName_Throws() var target = new ClickableControl(); var command = ReactiveCommand.Create(() => { }, outputScheduler: Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject( command, target, @@ -409,7 +409,7 @@ public void BindCommandToObject_WithAddRemoveHandlers_NullTarget_Throws() var binder = new CreatesCommandBindingViaEvent(); var command = ReactiveCommand.Create(() => { }, outputScheduler: Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject( command, null, @@ -426,7 +426,7 @@ public void BindCommandToObject_WithAddRemoveHandlers_NullAddHandler_Throws() var target = new ClickableControlWithGenericEvent(); var command = ReactiveCommand.Create(() => { }, outputScheduler: Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject( command, target, @@ -443,7 +443,7 @@ public void BindCommandToObject_WithAddRemoveHandlers_NullRemoveHandler_Throws() var target = new ClickableControlWithGenericEvent(); var command = ReactiveCommand.Create(() => { }, outputScheduler: Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => binder.BindCommandToObject( command, target, diff --git a/src/tests/ReactiveUI.Tests/Bindings/Converters/BindingFallbackConverterRegistryTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Converters/BindingFallbackConverterRegistryTests.cs new file mode 100644 index 0000000000..512525c1d4 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/Converters/BindingFallbackConverterRegistryTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Tests.Bindings.Converters; + +/// Tests for . +public class BindingFallbackConverterRegistryTests +{ + /// An empty registry returns an empty converter list. + /// A task representing the asynchronous operation. + [Test] + public async Task GetAllConvertersOnEmptyRegistryIsEmpty() + { + var registry = new BindingFallbackConverterRegistry(); + await Assert.That(registry.GetAllConverters()).IsEmpty(); + } + + /// A registered converter is returned by GetAllConverters. + /// A task representing the asynchronous operation. + [Test] + public async Task GetAllConvertersReturnsRegisteredConverter() + { + var registry = new BindingFallbackConverterRegistry(); + var converter = new StubFallbackConverter(); + registry.Register(converter); + + await Assert.That(registry.GetAllConverters()).Contains(converter); + } + + /// A fallback converter stub that never converts. + private sealed class StubFallbackConverter : IBindingFallbackConverter + { + /// + public int GetAffinityForObjects(Type fromType, Type toType) => 0; + + /// + public bool TryConvert(Type fromType, object from, Type toType, object? conversionHint, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? result) + { + result = null; + return false; + } + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/Converters/SetMethodBindingConverterRegistryTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Converters/SetMethodBindingConverterRegistryTests.cs new file mode 100644 index 0000000000..29d161f0f2 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Bindings/Converters/SetMethodBindingConverterRegistryTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Tests.Bindings.Converters; + +/// Tests for . +public class SetMethodBindingConverterRegistryTests +{ + /// An empty registry returns null from TryGetConverter and an empty converter list. + /// A task representing the asynchronous operation. + [Test] + public async Task EmptyRegistryHasNoConverters() + { + var registry = new SetMethodBindingConverterRegistry(); + await Assert.That(registry.TryGetConverter(typeof(string), typeof(int))).IsNull(); + await Assert.That(registry.GetAllConverters()).IsEmpty(); + } + + /// A registered converter is returned by GetAllConverters. + /// A task representing the asynchronous operation. + [Test] + public async Task GetAllConvertersReturnsRegisteredConverter() + { + var registry = new SetMethodBindingConverterRegistry(); + var converter = new StubSetMethodConverter(); + registry.Register(converter); + + await Assert.That(registry.GetAllConverters()).Contains(converter); + } + + /// A set-method converter stub that supports nothing. + private sealed class StubSetMethodConverter : ISetMethodBindingConverter + { + /// + public int GetAffinityForObjects(Type? fromType, Type? toType) => 0; + + /// + public object? PerformSet(object? toTarget, object? newValue, object?[]? arguments) => toTarget; + } +} diff --git a/src/tests/ReactiveUI.Tests/Bindings/Property/Mocks/MockPropertyBindingExpressionCompiler.cs b/src/tests/ReactiveUI.Tests/Bindings/Property/Mocks/MockPropertyBindingExpressionCompiler.cs index 89c8212a9b..2f5f5ab148 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/Property/Mocks/MockPropertyBindingExpressionCompiler.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/Property/Mocks/MockPropertyBindingExpressionCompiler.cs @@ -58,13 +58,7 @@ public void SetSetThenGetFunction(Func + return (Func?)_setThenGetFunc ?? ((target, value, parameters) => { var current = getter(target, parameters); if (EqualityComparer.Default.Equals(current, value)) @@ -74,7 +68,7 @@ public void SetSetThenGetFunction(Func diff --git a/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingConverterResolverTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingConverterResolverTests.cs index f1c8fc8bb7..6b32a7d290 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingConverterResolverTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingConverterResolverTests.cs @@ -201,7 +201,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(context); - builder + _ = builder .WithRegistration(r => r.RegisterConstant(new MockBindingTypeConverter())) .WithRegistration(r => r.RegisterConstant(new MockSetMethodBindingConverter())) diff --git a/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingHookEvaluatorTests.cs b/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingHookEvaluatorTests.cs index ef40d69191..09b2f26d88 100644 --- a/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingHookEvaluatorTests.cs +++ b/src/tests/ReactiveUI.Tests/Bindings/Property/Unit/BindingHookEvaluatorTests.cs @@ -225,7 +225,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(context); - builder + _ = builder .WithRegistration(r => r.RegisterConstant(new RejectingHook())) .WithCoreServices(); } diff --git a/src/tests/ReactiveUI.Tests/Builder/BuilderMixinsTests.cs b/src/tests/ReactiveUI.Tests/Builder/BuilderMixinsTests.cs new file mode 100644 index 0000000000..d29962790a --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Builder/BuilderMixinsTests.cs @@ -0,0 +1,158 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +#if REACTIVE_SHIM +using ReactiveUI.Reactive.Builder; +#else +using ReactiveUI.Builder; +#endif +using Splat; + +namespace ReactiveUI.Tests.Builder; + +/// Tests for the fluent converter/registration extension members on . +public class BuilderMixinsTests +{ + /// The four WithConverter overloads and WithConverters each register on and return the same builder. + /// A representing the asynchronous operation. + [Test] + public async Task WithConverterOverloadsReturnSameBuilder() + { + var builder = RxAppBuilder.CreateReactiveUIBuilder(); + + var afterTyped = BuilderMixins.WithConverter(builder, new StubTypedConverter()); + var afterInterface = BuilderMixins.WithConverter(builder, (IBindingTypeConverter)new StubTypedConverter()); + var afterTypedFactory = BuilderMixins.WithConverter(builder, static () => new StubTypedConverter()); + var afterInterfaceFactory = BuilderMixins.WithConverter(builder, static () => (IBindingTypeConverter)new StubTypedConverter()); + var afterMany = BuilderMixins.WithConverters(builder, new StubTypedConverter(), new StubTypedConverter()); + + using (Assert.Multiple()) + { + await Assert.That(afterTyped).IsSameReferenceAs(builder); + await Assert.That(afterInterface).IsSameReferenceAs(builder); + await Assert.That(afterTypedFactory).IsSameReferenceAs(builder); + await Assert.That(afterInterfaceFactory).IsSameReferenceAs(builder); + await Assert.That(afterMany).IsSameReferenceAs(builder); + } + } + + /// Both WithFallbackConverter overloads register on and return the same builder. + /// A representing the asynchronous operation. + [Test] + public async Task WithFallbackConverterOverloadsReturnSameBuilder() + { + var builder = RxAppBuilder.CreateReactiveUIBuilder(); + + var afterInstance = BuilderMixins.WithFallbackConverter(builder, new StubFallbackConverter()); + var afterFactory = BuilderMixins.WithFallbackConverter(builder, static () => (IBindingFallbackConverter)new StubFallbackConverter()); + + using (Assert.Multiple()) + { + await Assert.That(afterInstance).IsSameReferenceAs(builder); + await Assert.That(afterFactory).IsSameReferenceAs(builder); + } + } + + /// Both WithSetMethodConverter overloads register on and return the same builder. + /// A representing the asynchronous operation. + [Test] + public async Task WithSetMethodConverterOverloadsReturnSameBuilder() + { + var builder = RxAppBuilder.CreateReactiveUIBuilder(); + + var afterInstance = BuilderMixins.WithSetMethodConverter(builder, new StubSetMethodConverter()); + var afterFactory = BuilderMixins.WithSetMethodConverter(builder, static () => (ISetMethodBindingConverter)new StubSetMethodConverter()); + + using (Assert.Multiple()) + { + await Assert.That(afterInstance).IsSameReferenceAs(builder); + await Assert.That(afterFactory).IsSameReferenceAs(builder); + } + } + + /// Importing converters from a resolver returns the same builder. + /// A representing the asynchronous operation. + [Test] + public async Task WithConvertersFromReturnsSameBuilder() + { + var builder = RxAppBuilder.CreateReactiveUIBuilder(); + + var result = BuilderMixins.WithConvertersFrom(builder, new ModernDependencyResolver()); + + await Assert.That(result).IsSameReferenceAs(builder); + } + + /// Configuring the message bus and registering a constant view model each return the same builder. + /// A representing the asynchronous operation. + [Test] + public async Task WithMessageBusAndRegisterConstantViewModelReturnSameBuilder() + { + var builder = RxAppBuilder.CreateReactiveUIBuilder(); + + var afterBus = BuilderMixins.WithMessageBus(builder); + var afterViewModel = BuilderMixins.RegisterConstantViewModel(builder); + + using (Assert.Multiple()) + { + await Assert.That(afterBus).IsSameReferenceAs(builder); + await Assert.That(afterViewModel).IsSameReferenceAs(builder); + } + } + + /// A typed converter stub that performs no conversion. + /// The source type. + /// The target type. + private sealed class StubTypedConverter : BindingTypeConverter + { + /// + public override int GetAffinityForObjects() => 0; + + /// + public override bool TryConvert(TFrom? from, object? conversionHint, [NotNullWhen(true)] out TTo? result) + { + result = default; + return false; + } + } + + /// A fallback converter stub that performs no conversion. + private sealed class StubFallbackConverter : IBindingFallbackConverter + { + /// + public int GetAffinityForObjects( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type fromType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type toType) => 0; + + /// + public bool TryConvert( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type fromType, + object from, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + Type toType, + object? conversionHint, + [NotNullWhen(true)] out object? result) + { + result = null; + return false; + } + } + + /// A set-method converter stub that echoes the supplied value. + private sealed class StubSetMethodConverter : ISetMethodBindingConverter + { + /// + public int GetAffinityForObjects(Type? fromType, Type? toType) => 0; + + /// + public object? PerformSet(object? toTarget, object? newValue, object?[]? arguments) => newValue; + } + + /// A minimal reactive view model used for constant registration. + private sealed class StubViewModel : ReactiveObject; +} diff --git a/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs b/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs index 53f6f8ed59..c7b49c21d9 100644 --- a/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs +++ b/src/tests/ReactiveUI.Tests/ChangeSetMixinTest.cs @@ -22,7 +22,7 @@ public async Task WhenCountChanged_FiltersToOnlyCountChanges() var subject = new Signal>(); var results = new List>(); - subject.WhenCountChanged().ObserveOn(Sequencer.Immediate).Subscribe(results.Add); + _ = subject.WhenCountChanged().ObserveOn(Sequencer.Immediate).Subscribe(results.Add); var addChangeSet = new ReactiveChangeSet([new(ReactiveChangeReason.Add, 1, default, 0, -1)]); var updateChangeSet = new ReactiveChangeSet([new(ReactiveChangeReason.Replace, ReplacedItemValue, 1, 0, 0)]); diff --git a/src/tests/ReactiveUI.Tests/ChangeSets/ChangeSetExtensionsTests.cs b/src/tests/ReactiveUI.Tests/ChangeSets/ChangeSetExtensionsTests.cs new file mode 100644 index 0000000000..fbf9e48a3b --- /dev/null +++ b/src/tests/ReactiveUI.Tests/ChangeSets/ChangeSetExtensionsTests.cs @@ -0,0 +1,127 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Collections; +using System.Collections.Specialized; + +namespace ReactiveUI.Tests.ChangeSets; + +/// Tests for the change-set translation edge cases in . +public class ChangeSetExtensionsTests +{ + /// An add with an unknown (-1) starting index appends to the end of the shadow. + /// A representing the asynchronous operation. + [Test] + public async Task AddWithUnknownIndexAppends() + { + var collection = new RaisingCollection("a", "b"); + var sets = new List>(); + using var subscription = ChangeSetExtensions.ToReactiveChangeSet(collection).Subscribe(sets.Add); + + collection.Raise(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, "c", -1)); + + var last = sets[^1]; + using (Assert.Multiple()) + { + await Assert.That(last[0].Reason).IsEqualTo(ReactiveChangeReason.Add); + await Assert.That(last[0].Current).IsEqualTo("c"); + } + } + + /// A remove with an unknown (-1) starting index removes the item by value from the shadow. + /// A representing the asynchronous operation. + [Test] + public async Task RemoveWithUnknownIndexRemovesByValue() + { + var collection = new RaisingCollection("a", "b"); + var sets = new List>(); + using var subscription = ChangeSetExtensions.ToReactiveChangeSet(collection).Subscribe(sets.Add); + + collection.Raise(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, "a", -1)); + + var last = sets[^1]; + using (Assert.Multiple()) + { + await Assert.That(last[0].Reason).IsEqualTo(ReactiveChangeReason.Remove); + await Assert.That(last[0].Current).IsEqualTo("a"); + } + } + + /// A move event is translated into a move change. + /// A representing the asynchronous operation. + [Test] + public async Task MoveEmitsMoveChange() + { + var collection = new RaisingCollection("a", "b"); + var sets = new List>(); + using var subscription = ChangeSetExtensions.ToReactiveChangeSet(collection).Subscribe(sets.Add); + + collection.Raise(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, "b", 0, 1)); + + var last = sets[^1]; + await Assert.That(last[0].Reason).IsEqualTo(ReactiveChangeReason.Move); + } + + /// A reset re-emits a remove for each prior item followed by an add for each surviving item. + /// A representing the asynchronous operation. + [Test] + public async Task ResetReEmitsRemovesThenAdds() + { + var collection = new RaisingCollection("a", "b"); + var sets = new List>(); + using var subscription = ChangeSetExtensions.ToReactiveChangeSet(collection).Subscribe(sets.Add); + + collection.Raise(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + var last = sets[^1]; + using (Assert.Multiple()) + { + await Assert.That(last.Removes).IsGreaterThan(0); + await Assert.That(last.Adds).IsGreaterThan(0); + } + } + + /// A reset over an empty collection produces no changes and emits nothing beyond the initial batch. + /// A representing the asynchronous operation. + [Test] + public async Task ResetOnEmptyCollectionEmitsNoFurtherChangeSet() + { + var collection = new RaisingCollection(); + var sets = new List>(); + using var subscription = ChangeSetExtensions.ToReactiveChangeSet(collection).Subscribe(sets.Add); + + // Initial (empty) batch only. + var initialCount = sets.Count; + + // A reset with an empty shadow and empty collection yields zero changes, so nothing is emitted. + collection.Raise(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + await Assert.That(sets).Count().IsEqualTo(initialCount); + } + + /// A notifying collection whose events are raised manually, used to drive the edge-case translation paths. + private sealed class RaisingCollection : INotifyCollectionChanged, IEnumerable + { + /// The backing items. + private readonly List _items; + + /// Initializes a new instance of the class. + /// The initial items. + public RaisingCollection(params string[] items) => _items = [.. items]; + + /// + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + /// Raises the event with the supplied arguments. + /// The collection-changed event arguments. + public void Raise(NotifyCollectionChangedEventArgs e) => CollectionChanged?.Invoke(this, e); + + /// + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); + } +} diff --git a/src/tests/ReactiveUI.Tests/CommandBinding/CommandBindingTests.cs b/src/tests/ReactiveUI.Tests/CommandBinding/CommandBindingTests.cs index 7c8c856488..cdc2b39187 100644 --- a/src/tests/ReactiveUI.Tests/CommandBinding/CommandBindingTests.cs +++ b/src/tests/ReactiveUI.Tests/CommandBinding/CommandBindingTests.cs @@ -45,7 +45,7 @@ public async Task CommandBinderImplementation_Should_Bind_Command_To_Event() await Assert.That(disp).IsNotNull(); var executed = false; - viewModel.Command.Subscribe(_ => executed = true); + _ = viewModel.Command.Subscribe(_ => executed = true); view.Control.RaiseClick(); @@ -84,7 +84,7 @@ protected override void ConfigureAppBuilder(IReactiveUIBuilder builder, TestCont var scheduler = Sequencer.Immediate; - builder + _ = builder .WithMainThreadScheduler(scheduler) .WithTaskPoolScheduler(scheduler) .WithRegistration(r => r.RegisterConstant(new CreatesCommandBindingViaEvent())) diff --git a/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs b/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs index e704b7174b..4f069dde79 100644 --- a/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs +++ b/src/tests/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs @@ -119,8 +119,8 @@ public async Task ExceptionsAreDeliveredOnOutputScheduler() var childCommands = new[] { child }; var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler); Exception? exception = null; - fixture.ThrownExceptions.Subscribe(ex => exception = ex); - fixture.Execute().Subscribe(_ => { }, _ => { }); + _ = fixture.ThrownExceptions.Subscribe(ex => exception = ex); + _ = fixture.Execute().Subscribe(_ => { }, _ => { }); // With ImmediateScheduler, exceptions are delivered immediately await Assert.That(exception).IsTypeOf(); @@ -148,7 +148,7 @@ public async Task ExecuteExecutesAllChildCommands() var child2IsExecuting = child2.IsExecuting.Collect(); var child3IsExecuting = child3.IsExecuting.Collect(); - fixture.Execute().Subscribe(); + _ = fixture.Execute().Subscribe(); await Assert.That(isExecuting).Count().IsEqualTo(ExpectedExecutionEmissions); using (Assert.Multiple()) @@ -201,7 +201,7 @@ public async Task ExecuteTicksErrorsInAnyChildCommandThroughThrownExceptions() var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: Sequencer.Immediate); var thrownExceptions = fixture.ThrownExceptions.Collect(); - fixture.Execute().Subscribe(static _ => { }, static _ => { }); + _ = fixture.Execute().Subscribe(static _ => { }, static _ => { }); await Assert.That(thrownExceptions).Count().IsEqualTo(1); await Assert.That(thrownExceptions[0].Message).IsEqualTo("oops"); @@ -223,7 +223,7 @@ public async Task ExecuteTicksThroughTheResults() var results = fixture.Collect(); - fixture.Execute().Subscribe(); + _ = fixture.Execute().Subscribe(); await Assert.That(results).Count().IsEqualTo(1); await Assert.That(results[0]).Count().IsEqualTo(ExpectedChildResultCount); @@ -247,7 +247,7 @@ public async Task ResultIsTickedThroughSpecifiedScheduler() var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler); var results = fixture.Collect(); - fixture.Execute().Subscribe(); + _ = fixture.Execute().Subscribe(); // With ImmediateScheduler, results are delivered immediately await Assert.That(results).Count().IsEqualTo(1); diff --git a/src/tests/ReactiveUI.Tests/Commands/CreatesCommandBindingTests.cs b/src/tests/ReactiveUI.Tests/Commands/CreatesCommandBindingTests.cs index c90588706f..a17bf3f0bf 100644 --- a/src/tests/ReactiveUI.Tests/Commands/CreatesCommandBindingTests.cs +++ b/src/tests/ReactiveUI.Tests/Commands/CreatesCommandBindingTests.cs @@ -4,13 +4,28 @@ // See the LICENSE file in the project root for full license information. using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using ReactiveUI.Tests.ReactiveObjects.Mocks; +using Splat; namespace ReactiveUI.Tests.Commands; /// Tests for command binding creation. public class CreatesCommandBindingTests { + /// Binding throws when no command binder is registered for the target type. + /// A representing the asynchronous operation. + [Test] + [NotInParallel] + public async Task BindCommandToObjectThrowsWhenNoBinderFound() + { + using var locator = new ModernDependencyResolver(); + using (locator.WithResolver()) + { + await Assert.That(() => Bind(new TestFixture())).Throws(); + } + } + /// Test that makes sure events binder binds to explicit event. /// A representing the asynchronous operation. [Test] @@ -40,4 +55,11 @@ public async Task EventBinderBindsToExplicitEvent() input.IsNotNullString = "Bar"; await Assert.That(wasCalled).IsFalse(); } + + /// Invokes the default command binder lookup for the supplied target. + /// The target to bind the command to. + /// The binding disposable. + [RequiresUnreferencedCode("Exercises the reflection-based command binder lookup.")] + private static IDisposable Bind(TestFixture target) => + CreatesCommandBinding.BindCommandToObject(ReactiveCommand.Create(() => { }), target, Signal.Silent()); } diff --git a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.CreationTasks.cs b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.CreationTasks.cs index eba1f36500..9214f3aa64 100644 --- a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.CreationTasks.cs +++ b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.CreationTasks.cs @@ -28,21 +28,21 @@ public async Task CreateFromTask_Cancellable_ProperlyCancelsExecution() var fixture = ReactiveCommand.CreateFromTask( async token => { - tcsStarted.TrySetResult(RxVoid.Default); + _ = tcsStarted.TrySetResult(RxVoid.Default); try { await Task.Delay(LongDelayMilliseconds, token); } catch (OperationCanceledException) { - tcsCaught.TrySetResult(RxVoid.Default); + _ = tcsCaught.TrySetResult(RxVoid.Default); await tcsFinish.Task; throw; } }, outputScheduler: Sequencer.Immediate); - fixture.ThrownExceptions.Subscribe(_ => { }); + _ = fixture.ThrownExceptions.Subscribe(_ => { }); var disposable = fixture.Execute().Subscribe(); @@ -50,7 +50,7 @@ public async Task CreateFromTask_Cancellable_ProperlyCancelsExecution() disposable.Dispose(); await tcsCaught.Task.WaitAsync(TimeSpan.FromSeconds(WaitTimeoutSeconds)); - tcsFinish.TrySetResult(RxVoid.Default); + _ = tcsFinish.TrySetResult(RxVoid.Default); // Wait for cancellation to complete await Task.Delay(CompletionDelayMilliseconds); diff --git a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.ExecutionAndInvoke.cs b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.ExecutionAndInvoke.cs index 37fc92fef9..87601591df 100644 --- a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.ExecutionAndInvoke.cs +++ b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.ExecutionAndInvoke.cs @@ -58,7 +58,7 @@ public async Task Execute_LazyEvaluation() var execution = command.Execute(); await Assert.That(executionCount).IsEqualTo(0); - execution.Subscribe(); + _ = execution.Subscribe(); await Assert.That(executionCount).IsEqualTo(1); } @@ -122,9 +122,9 @@ public async Task Execute_ReenablesAfterFailure() () => Signal.Fail(new InvalidOperationException(TestErrorMessage)), outputScheduler: Sequencer.Immediate); var canExecute = command.CanExecute.Collect(); - command.ThrownExceptions.Subscribe(); + _ = command.ThrownExceptions.Subscribe(); - command.Execute().Subscribe(_ => { }, _ => { }); + _ = command.Execute().Subscribe(_ => { }, _ => { }); const int ExpectedCount = 3; const int ThirdIndex = 2; @@ -299,7 +299,7 @@ public async Task InvokeCommand_ICommand_InvokesCommand() () => ++executionCount, outputScheduler: Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); const int ExpectedSecondCount = 2; @@ -323,7 +323,7 @@ public async Task InvokeCommand_ICommand_PassesParameter() receivedParams.Add, outputScheduler: Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); source.OnNext(ParameterValue); source.OnNext(SecondParameter); @@ -348,7 +348,7 @@ public async Task InvokeCommand_ICommand_RespectsCanExecute() canExecute, Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); source.OnNext(RxVoid.Default); await Assert.That(executed).IsFalse(); @@ -368,7 +368,7 @@ public async Task InvokeCommand_ICommand_WorksWithColdObservable() () => ++executionCount, outputScheduler: Sequencer.Immediate); var source = Signal.Emit(RxVoid.Default); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); await Assert.That(executionCount).IsEqualTo(1); } @@ -381,7 +381,7 @@ public async Task InvokeCommand_ICommandInTarget_InvokesCommand() var executionCount = 0; var target = new CommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); target.TheCommand = ReactiveCommand.Create( () => ++executionCount, outputScheduler: Sequencer.Immediate); @@ -402,7 +402,7 @@ public async Task InvokeCommand_ICommandInTarget_PassesParameter() { var target = new CommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); var command = new FakeCommand(); target.TheCommand = command; @@ -424,7 +424,7 @@ public async Task InvokeCommand_ICommandInTarget_RespectsCanExecute() var canExecute = new BehaviorSignal(false); var target = new CommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); target.TheCommand = ReactiveCommand.Create( () => executed = true, canExecute, @@ -447,7 +447,7 @@ public async Task InvokeCommand_ICommandInTarget_RespectsCanExecuteWindow() var canExecute = new BehaviorSignal(false); var target = new CommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); target.TheCommand = ReactiveCommand.Create( () => executed = true, canExecute, @@ -475,10 +475,10 @@ public async Task InvokeCommand_ICommandInTarget_SwallowsExceptions() throw new InvalidOperationException(); }, outputScheduler: Sequencer.Immediate); - command.ThrownExceptions.Subscribe(); + _ = command.ThrownExceptions.Subscribe(); target.TheCommand = command; var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); const int ExpectedCount = 2; @@ -498,7 +498,7 @@ public async Task InvokeCommand_ReactiveCommand_InvokesCommand() () => ++executionCount, outputScheduler: Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); const int ExpectedSecondCount = 2; @@ -522,7 +522,7 @@ public async Task InvokeCommand_ReactiveCommand_PassesParameter() receivedParams.Add, outputScheduler: Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); source.OnNext(ParameterValue); source.OnNext(SecondParameter); @@ -547,7 +547,7 @@ public async Task InvokeCommand_ReactiveCommand_RespectsCanExecute() canExecute, Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); source.OnNext(RxVoid.Default); await Assert.That(executed).IsFalse(); @@ -569,7 +569,7 @@ public async Task InvokeCommand_ReactiveCommand_RespectsCanExecuteWindow() canExecute, Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); source.OnNext(RxVoid.Default); await Assert.That(executed).IsFalse(); @@ -592,9 +592,9 @@ public async Task InvokeCommand_ReactiveCommand_SwallowsExceptions() throw new InvalidOperationException(); }, outputScheduler: Sequencer.Immediate); - command.ThrownExceptions.Subscribe(); + _ = command.ThrownExceptions.Subscribe(); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); const int ExpectedCount = 2; @@ -612,7 +612,7 @@ public async Task InvokeCommand_ReactiveCommandInTarget_InvokesCommand() var executionCount = 0; var target = new ReactiveCommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); target.TheCommand = ReactiveCommand.Create( _ => ++executionCount, outputScheduler: Sequencer.Immediate); @@ -634,7 +634,7 @@ public async Task InvokeCommand_ReactiveCommandInTarget_PassesParameter() var receivedParam = 0; var target = new ReactiveCommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); target.TheCommand = ReactiveCommand.Create( param => receivedParam = param, outputScheduler: Sequencer.Immediate); @@ -652,7 +652,7 @@ public async Task InvokeCommand_ReactiveCommandInTarget_RespectsCanExecute() var canExecute = new BehaviorSignal(false); var target = new ReactiveCommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); target.TheCommand = ReactiveCommand.Create( _ => executed = true, canExecute, @@ -675,7 +675,7 @@ public async Task InvokeCommand_ReactiveCommandInTarget_RespectsCanExecuteWindow var canExecute = new BehaviorSignal(false); var target = new ReactiveCommandHolder(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); target.TheCommand = ReactiveCommand.Create( _ => executed = true, canExecute, @@ -705,9 +705,9 @@ public async Task InvokeCommand_ReactiveCommandInTarget_SwallowsExceptions() }, outputScheduler: Sequencer.Immediate) }; - target.TheCommand.ThrownExceptions.Subscribe(); + _ = target.TheCommand.ThrownExceptions.Subscribe(); var source = new Signal(); - source.InvokeCommand(target, x => x.TheCommand!); + _ = source.InvokeCommand(target, x => x.TheCommand!); const int ExpectedCount = 2; diff --git a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.IsExecutingAndExceptions.cs b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.IsExecutingAndExceptions.cs index e422947119..fdcc68ab34 100644 --- a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.IsExecutingAndExceptions.cs +++ b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.IsExecutingAndExceptions.cs @@ -66,7 +66,7 @@ public async Task IsExecuting_RemainsTrue_UntilExecutionCompletes() () => executeSubject, outputScheduler: Sequencer.Immediate); - command.Execute().Subscribe(); + _ = command.Execute().Subscribe(); await Assert.That(await command.IsExecuting.FirstAsync()).IsTrue(); @@ -90,7 +90,7 @@ public async Task IsExecuting_TicksWhileExecuting() outputScheduler: scheduler); var isExecuting = command.IsExecuting.Collect(); - command.Execute().Subscribe(); + _ = command.Execute().Subscribe(); scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); const int ExpectedCount = 2; @@ -184,7 +184,7 @@ public async Task Scheduler_ResultsDeliveredOnOutputScheduler() outputScheduler: scheduler); var executed = false; - command.Execute().Subscribe(_ => executed = true); + _ = command.Execute().Subscribe(_ => executed = true); await Assert.That(executed).IsTrue(); } @@ -208,7 +208,7 @@ public async Task Task_Cancellation_HandlesProperCancellationFlow() async token => { statusTrail.Add((Interlocked.Increment(ref position) - 1, StartedCommandStatus)); - tcsStarted.TrySetResult(RxVoid.Default); + _ = tcsStarted.TrySetResult(RxVoid.Default); try { await Task.Delay(LongDelayMilliseconds, token); @@ -216,7 +216,7 @@ public async Task Task_Cancellation_HandlesProperCancellationFlow() catch (OperationCanceledException) { statusTrail.Add((Interlocked.Increment(ref position) - 1, CancellingCommandStatus)); - tcsCaught.TrySetResult(RxVoid.Default); + _ = tcsCaught.TrySetResult(RxVoid.Default); await tcsFinish.Task; statusTrail.Add((Interlocked.Increment(ref position) - 1, FinishedCancellingStatus)); throw; @@ -227,9 +227,9 @@ public async Task Task_Cancellation_HandlesProperCancellationFlow() outputScheduler: Sequencer.Immediate); Exception? exception = null; - command.ThrownExceptions.Subscribe(ex => exception = ex); + _ = command.ThrownExceptions.Subscribe(ex => exception = ex); var latestIsExecutingValue = false; - command.IsExecuting.Subscribe(isExec => + _ = command.IsExecuting.Subscribe(isExec => { statusTrail.Add((Interlocked.Increment(ref position) - 1, $"executing = {isExec}")); Volatile.Write(ref latestIsExecutingValue, isExec); @@ -245,7 +245,7 @@ public async Task Task_Cancellation_HandlesProperCancellationFlow() await tcsCaught.Task.WaitAsync(TimeSpan.FromSeconds(WaitTimeoutSeconds)); await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - tcsFinish.TrySetResult(RxVoid.Default); + _ = tcsFinish.TrySetResult(RxVoid.Default); await Task.Delay(CompletionDelayMilliseconds); const int StartedCommandPosition = 2; @@ -288,7 +288,7 @@ public async Task Task_Completion_HandlesProperCompletionFlow() async cts => { statusTrail.Add((Interlocked.Increment(ref position) - 1, StartedCommandStatus)); - tcsStarted.TrySetResult(RxVoid.Default); + _ = tcsStarted.TrySetResult(RxVoid.Default); try { await Task.Delay(DelayMilliseconds, cts); @@ -302,23 +302,23 @@ public async Task Task_Completion_HandlesProperCompletionFlow() } statusTrail.Add((Interlocked.Increment(ref position) - 1, "finished command")); - tcsFinished.TrySetResult(RxVoid.Default); + _ = tcsFinished.TrySetResult(RxVoid.Default); await tcsContinue.Task; return RxVoid.Default; }, outputScheduler: Sequencer.Immediate); Exception? exception = null; - command.ThrownExceptions.Subscribe(ex => exception = ex); + _ = command.ThrownExceptions.Subscribe(ex => exception = ex); var latestIsExecutingValue = false; - command.IsExecuting.Subscribe(isExec => + _ = command.IsExecuting.Subscribe(isExec => { statusTrail.Add((Interlocked.Increment(ref position) - 1, $"executing = {isExec}")); Volatile.Write(ref latestIsExecutingValue, isExec); }); var result = false; - command.Execute().Subscribe(_ => result = true); + _ = command.Execute().Subscribe(_ => result = true); await tcsStarted.Task.WaitAsync(TimeSpan.FromSeconds(WaitTimeoutSeconds)); await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); @@ -326,7 +326,7 @@ public async Task Task_Completion_HandlesProperCompletionFlow() await tcsFinished.Task.WaitAsync(TimeSpan.FromSeconds(WaitTimeoutSeconds)); await Assert.That(Volatile.Read(ref latestIsExecutingValue)).IsTrue(); - tcsContinue.TrySetResult(RxVoid.Default); + _ = tcsContinue.TrySetResult(RxVoid.Default); await Task.Delay(CompletionDelayMilliseconds); const int StartedCommandPosition = 2; @@ -366,7 +366,7 @@ public async Task Task_Exception_HandlesExceptionFlow() const int DelayMilliseconds = 100; - command.Execute().Subscribe(); + _ = command.Execute().Subscribe(); await Task.Delay(DelayMilliseconds); tcsStart.SetResult(RxVoid.Default); @@ -391,7 +391,7 @@ public async Task ThrownExceptions_CapturesLambdaExceptions() outputScheduler: Sequencer.Immediate); var exceptions = command.ThrownExceptions.Collect(); - command.Execute().Subscribe(_ => { }, _ => { }); + _ = command.Execute().Subscribe(_ => { }, _ => { }); await Assert.That(exceptions).Count().IsEqualTo(1); await Assert.That(exceptions[0]).IsTypeOf(); @@ -408,7 +408,7 @@ public async Task ThrownExceptions_CapturesObservableExceptions() outputScheduler: Sequencer.Immediate); var exceptions = command.ThrownExceptions.Collect(); - command.Execute().Subscribe(_ => { }, _ => { }); + _ = command.Execute().Subscribe(_ => { }, _ => { }); await Assert.That(exceptions).Count().IsEqualTo(1); await Assert.That(exceptions[0]).IsTypeOf(); @@ -425,9 +425,9 @@ public async Task ThrownExceptions_DeliveredOnOutputScheduler() () => Signal.Fail(new InvalidOperationException()), outputScheduler: scheduler); Exception? exception = null; - command.ThrownExceptions.Subscribe(ex => exception = ex); + _ = command.ThrownExceptions.Subscribe(ex => exception = ex); - command.Execute().Subscribe(_ => { }, _ => { }); + _ = command.Execute().Subscribe(_ => { }, _ => { }); await Assert.That(exception).IsTypeOf(); } @@ -449,7 +449,7 @@ public async Task ThrownExceptions_PropagatesTaskExceptions() const int DelayMilliseconds = 100; - command.Execute().Subscribe(); + _ = command.Execute().Subscribe(); await Task.Delay(DelayMilliseconds); tcsStart.SetResult(RxVoid.Default); diff --git a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs index 3b405c0b95..49095fa6e0 100644 --- a/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs +++ b/src/tests/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs @@ -71,7 +71,7 @@ public async Task CanExecute_IsFalseWhileExecuting() outputScheduler: scheduler); var canExecute = command.CanExecute.Collect(); - command.Execute().Subscribe(); + _ = command.Execute().Subscribe(); scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1)); const int ExpectedCount = 2; @@ -201,7 +201,7 @@ public async Task Create_Action_RespectsCanExecute() Sequencer.Immediate); var source = new Signal(); - source.InvokeCommand(command); + _ = source.InvokeCommand(command); source.OnNext(RxVoid.Default); await Assert.That(executed).IsFalse(); @@ -497,7 +497,7 @@ public async Task CreateCombined_PropagatesChildExceptions() const int Parameter = 5; - combined.Execute(Parameter).Subscribe(_ => { }, _ => { }); + _ = combined.Execute(Parameter).Subscribe(_ => { }, _ => { }); await Assert.That(exceptions).Count().IsEqualTo(1); await Assert.That(exceptions[0]).IsTypeOf(); diff --git a/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs b/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs index bbe0fa8545..0030021649 100644 --- a/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs +++ b/src/tests/ReactiveUI.Tests/Comparers/OrderedComparerTests.cs @@ -93,7 +93,7 @@ public async Task SmokeTest() public async Task WorksWithAnonymousTypes() { var source = new List { "abc", "bcd", "cde" }; - var items = source.ConvertAll(static x => new { FirstLetter = x[0], AllOfIt = x }); + var items = source.ConvertAll(static x => (FirstLetter: x[0], AllOfIt: x)); items.Sort(OrderedComparer.For(items).OrderBy(static x => x.FirstLetter)); await Assert.That(items.Select(static x => x.FirstLetter).SequenceEqual("abc")).IsTrue(); diff --git a/src/tests/ReactiveUI.Tests/Core/ObservablesTests.cs b/src/tests/ReactiveUI.Tests/Core/ObservablesTests.cs index dd0abeeb0d..a809908ff6 100644 --- a/src/tests/ReactiveUI.Tests/Core/ObservablesTests.cs +++ b/src/tests/ReactiveUI.Tests/Core/ObservablesTests.cs @@ -21,7 +21,7 @@ public async Task Observables_False_EmitsFalse() bool? result = null; // Act - SingleValueObservable.False.Subscribe(x => result = x); + _ = SingleValueObservable.False.Subscribe(x => result = x); // Assert await Assert.That(result).IsFalse(); @@ -50,7 +50,7 @@ public async Task Observables_True_EmitsTrue() bool? result = null; // Act - SingleValueObservable.True.Subscribe(x => result = x); + _ = SingleValueObservable.True.Subscribe(x => result = x); // Assert await Assert.That(result).IsTrue(); @@ -65,7 +65,7 @@ public async Task Observables_Unit_EmitsUnitDefault() RxVoid? result = null; // Act - SingleValueObservable.Void.Subscribe(x => result = x); + _ = SingleValueObservable.Void.Subscribe(x => result = x); // Assert await Assert.That(result).IsEqualTo(RxVoid.Default); diff --git a/src/tests/ReactiveUI.Tests/Expressions/CompiledPropertyChainTests.cs b/src/tests/ReactiveUI.Tests/Expressions/CompiledPropertyChainTests.cs index 9c635ac95e..e6c2fd3f39 100644 --- a/src/tests/ReactiveUI.Tests/Expressions/CompiledPropertyChainTests.cs +++ b/src/tests/ReactiveUI.Tests/Expressions/CompiledPropertyChainTests.cs @@ -118,7 +118,7 @@ public async Task CompiledPropertyChain_TryGetAllValues_WithNullIntermediate_Ret [Test] public async Task CompiledPropertyChain_Constructor_WithEmptyChain_Throws() { - var emptyChain = Array.Empty(); + var emptyChain = Array.Empty(); await Assert.That(() => new Reflection.CompiledPropertyChain(emptyChain)) .Throws(); @@ -236,7 +236,7 @@ public async Task CompiledPropertyChainSetter_WithSingleLevelProperty_SetsDirect [Test] public async Task CompiledPropertyChainSetter_Constructor_WithEmptyChain_Throws() { - var emptyChain = Array.Empty(); + var emptyChain = Array.Empty(); await Assert.That(() => new Reflection.CompiledPropertyChainSetter(emptyChain)) .Throws(); diff --git a/src/tests/ReactiveUI.Tests/Expressions/ExpressionMixinsTests.cs b/src/tests/ReactiveUI.Tests/Expressions/ExpressionMixinsTests.cs index ae6b9b7803..21d58d5395 100644 --- a/src/tests/ReactiveUI.Tests/Expressions/ExpressionMixinsTests.cs +++ b/src/tests/ReactiveUI.Tests/Expressions/ExpressionMixinsTests.cs @@ -42,4 +42,51 @@ public async Task GetMemberInfo_PropertyExpression_ReturnsPropertyName() await Assert.That(memberInfo).IsNotNull(); await Assert.That(memberInfo!.Name).IsEqualTo("IsNotNullString"); } + + /// The chain normalizes both a nested indexer (receiver rewritten to a parameter placeholder) and a + /// top-level indexer (receiver already the parameter, left unchanged), covering both branches of the index + /// normalization. + /// A representing the asynchronous operation. + [Test] + public async Task GetExpressionChainNormalizesNestedAndTopLevelIndexers() + { + Expression> nested = x => x.Leaf[0]; + Expression> topLevel = x => x[0]; + + // Rewrite turns the get_Item method calls into IndexExpression nodes, which is what GetExpressionChain + // normalizes. The nested receiver (x.Leaf) is not the parameter, so it is rewritten; the top-level receiver + // (x) already is the parameter, so it is left as-is. + const int NestedChainLength = 2; + const int TopLevelChainLength = 1; + + var nestedChain = Reflection.Rewrite(nested.Body).GetExpressionChain().ToList(); + var topLevelChain = Reflection.Rewrite(topLevel.Body).GetExpressionChain().ToList(); + + using (Assert.Multiple()) + { + await Assert.That(nestedChain).Count().IsEqualTo(NestedChainLength); + await Assert.That(topLevelChain).Count().IsEqualTo(TopLevelChainLength); + } + } + + /// A root object exposing both a nested indexer and a top-level indexer. + private sealed class IndexerRoot + { + /// Gets a nested object that itself exposes an indexer. + public IndexerLeaf Leaf { get; } = new(); + + /// Gets the value at the specified index. + /// The index. + /// An empty string. + public string this[int index] => string.Empty; + } + + /// A leaf object exposing an indexer reached through a nested member access. + private sealed class IndexerLeaf + { + /// Gets the value at the specified index. + /// The index. + /// An empty string. + public string this[int index] => string.Empty; + } } diff --git a/src/tests/ReactiveUI.Tests/Expressions/ExpressionRewriterTests.cs b/src/tests/ReactiveUI.Tests/Expressions/ExpressionRewriterTests.cs index d010cf9890..995ca865c9 100644 --- a/src/tests/ReactiveUI.Tests/Expressions/ExpressionRewriterTests.cs +++ b/src/tests/ReactiveUI.Tests/Expressions/ExpressionRewriterTests.cs @@ -29,7 +29,7 @@ public void Rewrite_WithArrayIndexNonConstant_Throws() // x.Index is a non-constant member access (not foldable to a constant index), so the rewrite is unsupported. Expression> expr = x => x.Array[x.Index]; - Assert.Throws(() => Reflection.Rewrite(expr.Body)); + _ = Assert.Throws(() => Reflection.Rewrite(expr.Body)); } /// Verifies that an array length expression is rewritten to a Length member access. @@ -95,7 +95,7 @@ public void Rewrite_WithIndexExpressionNonConstantArguments_Throws() var nonConstantArg = System.Linq.Expressions.Expression.Parameter(typeof(int), "index"); var indexExpr = System.Linq.Expressions.Expression.MakeIndex(listProperty, indexer, [nonConstantArg]); - Assert.Throws(() => Reflection.Rewrite(indexExpr)); + _ = Assert.Throws(() => Reflection.Rewrite(indexExpr)); } /// Verifies that a list indexer expression is rewritten to an Index node. @@ -110,6 +110,21 @@ public async Task Rewrite_WithListIndexer_ReturnsIndexExpression() await Assert.That(result.NodeType).IsEqualTo(ExpressionType.Index); } + /// Verifies that an already-built index expression with constant arguments is visited and preserved as an Index node. + /// A representing the asynchronous operation. + [Test] + public async Task Rewrite_WithConstantIndexExpression_VisitsAndReturnsIndexExpression() + { + var parameter = Expression.Parameter(typeof(TestClass), "x"); + var listProperty = Expression.Property(parameter, "List"); + var indexer = typeof(List).GetProperty("Item")!; + var indexExpr = Expression.MakeIndex(listProperty, indexer, [Expression.Constant(0)]); + + var result = Reflection.Rewrite(indexExpr); + + await Assert.That(result.NodeType).IsEqualTo(ExpressionType.Index); + } + /// Verifies that a non-constant list indexer throws a not supported exception. [Test] public void Rewrite_WithListIndexerNonConstant_Throws() @@ -117,7 +132,7 @@ public void Rewrite_WithListIndexerNonConstant_Throws() // x.Index is a non-constant member access (not foldable to a constant index), so the rewrite is unsupported. Expression> expr = x => x.List[x.Index]; - Assert.Throws(() => Reflection.Rewrite(expr.Body)); + _ = Assert.Throws(() => Reflection.Rewrite(expr.Body)); } /// Verifies that a member access expression is preserved as a MemberAccess node. @@ -138,7 +153,7 @@ public void Rewrite_WithMethodCallNonSpecialName_Throws() { Expression> expr = x => x.GetValue(); - Assert.Throws(() => Reflection.Rewrite(expr.Body)); + _ = Assert.Throws(() => Reflection.Rewrite(expr.Body)); } /// Verifies that a nested member access expression is preserved as a MemberAccess node. @@ -179,7 +194,7 @@ public void Rewrite_WithUnaryExpressionNotArrayLengthOrConvert_Throws() var parameter = System.Linq.Expressions.Expression.Parameter(typeof(bool), "x"); var notExpr = System.Linq.Expressions.Expression.Not(parameter); - Assert.Throws(() => Reflection.Rewrite(notExpr)); + _ = Assert.Throws(() => Reflection.Rewrite(notExpr)); } /// Verifies that an unsupported binary expression throws with a helpful message. diff --git a/src/tests/ReactiveUI.Tests/Expressions/ReflectionAdvancedTests.cs b/src/tests/ReactiveUI.Tests/Expressions/ReflectionAdvancedTests.cs index 61f7e7c09a..879040591c 100644 --- a/src/tests/ReactiveUI.Tests/Expressions/ReflectionAdvancedTests.cs +++ b/src/tests/ReactiveUI.Tests/Expressions/ReflectionAdvancedTests.cs @@ -23,6 +23,9 @@ public class ReflectionAdvancedTests /// The value stored against the dictionary key in reflection tests. private const int DictionaryValue = 42; + /// A sample string value used when exercising setter paths. + private const string SampleValue = "sample"; + /// Verifies that a complex nested expression is rewritten to a member access. /// A representing the asynchronous operation. [Test] @@ -183,7 +186,7 @@ public async Task TryGetAllValuesForPropertyChain_WithValidChain_ReturnsAllValue public async Task TryGetAllValuesForPropertyChain_WithEmptyChain_Throws() { var obj = new TestClass(); - var emptyChain = Array.Empty(); + var emptyChain = Array.Empty(); await Assert.That(() => Reflection.TryGetAllValuesForPropertyChain(out _, obj, emptyChain)) .Throws(); @@ -224,7 +227,7 @@ public async Task TrySetValueToPropertyChain_WithShouldThrowFalse_AndNullInterme public async Task TrySetValueToPropertyChain_WithEmptyChain_Throws() { var obj = new TestClass(); - var emptyChain = Array.Empty(); + var emptyChain = Array.Empty(); await Assert.That(() => Reflection.TrySetValueToPropertyChain(obj, emptyChain, "value")) .Throws(); @@ -335,6 +338,267 @@ public async Task IsStatic_WithGetOnlyStaticProperty_ReturnsTrue() await Assert.That(result).IsTrue(); } + /// Verifies that an index expression with multiple arguments separates them with a comma. + /// A representing the asynchronous operation. + [Test] + public async Task ExpressionToPropertyNames_WithMultiArgIndexer_SeparatesWithComma() + { + var parameter = Expression.Parameter(typeof(MultiIndexerClass), "x"); + var indexer = typeof(MultiIndexerClass).GetProperty(ItemPropertyName)!; + var firstArg = Expression.Constant(1); + var secondArg = Expression.Constant(2); + var indexExpr = Expression.MakeIndex(parameter, indexer, [firstArg, secondArg]); + + var result = Reflection.ExpressionToPropertyNames(indexExpr); + + await Assert.That(result).IsEqualTo("Item[1,2]"); + } + + /// Verifies that getting all values returns false when an intermediate link (before the last) is null. + /// A representing the asynchronous operation. + [Test] + public async Task TryGetAllValuesForPropertyChain_WithNullMidChain_ReturnsFalseInLoop() + { + var obj = new TestClass { Nested = null }; + Expression> expr = x => x.Nested!.Nested!.Nested!.Property; + var chain = expr.Body.GetExpressionChain(); + + var result = Reflection.TryGetAllValuesForPropertyChain(out var changeValues, obj, chain); + + await Assert.That(result).IsFalse(); + await Assert.That(changeValues[1]).IsNull(); + } + + /// Verifies that setting a value into a read-only property invokes the cached setter and surfaces its error. + /// A representing the asynchronous operation. + [Test] + public async Task TrySetValueToPropertyChain_WithReadOnlyProperty_InvokesSetterAndThrows() + { + var obj = new TestClass(); + Expression> expr = x => x.ReadOnlyProperty; + var chain = expr.Body.GetExpressionChain(); + + await Assert.That(() => Reflection.TrySetValueToPropertyChain(obj, chain, SampleValue, false)) + .Throws(); + } + + /// Verifies that the overload check throws when the object's runtime type lacks the requested method. + /// A representing the asynchronous operation. + [Test] + public async Task ThrowIfMethodsNotOverloaded_WithObjectMissingMethod_Throws() + { + var obj = new TestClass(); + + await Assert.That(() => + Reflection.ThrowIfMethodsNotOverloaded(TestCallerName, obj, "NonExistentMethod")) + .Throws(); + } + + /// Verifies that getting the args type for a real event returns the event args type. + /// A representing the asynchronous operation. + [Test] + public async Task GetEventArgsTypeForEvent_WithRealEvent_ReturnsEventArgsType() + { + var result = Reflection.GetEventArgsTypeForEvent( + typeof(EventClass), + nameof(EventClass.SampleEvent)); + + await Assert.That(result).IsEqualTo(typeof(EventArgs)); + } + + /// Verifies that an empty non-array collection chain throws the empty-chain error (materialization collection branch). + /// A representing the asynchronous operation. + [Test] + public async Task TryGetValueForPropertyChain_WithEmptyListChain_ThrowsInvalidOperation() + { + var obj = new TestClass(); + var emptyList = new List(); + + await Assert.That(() => Reflection.TryGetValueForPropertyChain(out _, obj, emptyList)) + .Throws(); + } + + /// Verifies that a non-empty list chain is materialized via the collection branch and traversed. + /// A representing the asynchronous operation. + [Test] + public async Task TryGetValueForPropertyChain_WithListChain_ReturnsValue() + { + var obj = new TestClass { Nested = new() { Property = "fromList" } }; + Expression> expr = x => x.Nested!.Property; + var list = new List(expr.Body.GetExpressionChain()); + + var result = Reflection.TryGetValueForPropertyChain(out var value, obj, list); + + await Assert.That(result).IsTrue(); + await Assert.That(value).IsEqualTo("fromList"); + } + + /// Verifies that a lazily-enumerated (non-collection) chain hits the default materialization path. + /// A representing the asynchronous operation. + [Test] + public async Task TryGetValueForPropertyChain_WithEnumerableChain_ReturnsValue() + { + var obj = new TestClass { Nested = new() { Property = "fromEnumerable" } }; + Expression> expr = x => x.Nested!.Property; + var enumerable = WrapAsEnumerable(expr.Body.GetExpressionChain()); + + var result = Reflection.TryGetValueForPropertyChain(out var value, obj, enumerable); + + await Assert.That(result).IsTrue(); + await Assert.That(value).IsEqualTo("fromEnumerable"); + } + + /// Verifies that ViewModelWhenAnyValue emits the observed view model property value. + /// A representing the asynchronous operation. + [Test] + public async Task ViewModelWhenAnyValue_EmitsObservedValue() + { + var view = new SampleView { ViewModel = new() { Name = "vmValue" } }; + Expression> expr = x => x.Name; + + object? observed = null; + using var subscription = Reflection + .ViewModelWhenAnyValue(view.ViewModel, view, expr.Body) + .Subscribe(x => observed = x); + + await Assert.That(observed).IsEqualTo("vmValue"); + } + + /// Verifies that a compiled property chain returns false when an intermediate link before the last is null. + /// A representing the asynchronous operation. + [Test] + public async Task CompiledPropertyChain_TryGetAllValues_WithNullMidChain_ReturnsFalseInLoop() + { + Expression> expr = x => x.Nested!.Nested!.Nested!.Property; + var chain = expr.Body.GetExpressionChain().ToArray(); + var compiled = new Reflection.CompiledPropertyChain(chain); + var obj = new TestClass { Nested = null }; + + var result = compiled.TryGetAllValues(obj, out var changeValues); + + await Assert.That(result).IsFalse(); + await Assert.That(changeValues[1]).IsNull(); + } + + /// Verifies that a compiled property chain returns false when an intermediate is null during value traversal. + /// A representing the asynchronous operation. + [Test] + public async Task CompiledPropertyChain_TryGetValue_WithNullMidChain_ReturnsFalse() + { + Expression> expr = x => x.Nested!.Nested!.Nested!.Property; + var chain = expr.Body.GetExpressionChain().ToArray(); + var compiled = new Reflection.CompiledPropertyChain(chain); + var obj = new TestClass { Nested = null }; + + var result = compiled.TryGetValue(obj, out var value); + + await Assert.That(result).IsFalse(); + await Assert.That(value).IsNull(); + } + + /// Verifies that a compiled setter throws for a read-only (unsettable) member when configured to throw. + /// A representing the asynchronous operation. + [Test] + public async Task CompiledPropertyChainSetter_TrySetValue_WithUnsettableMember_AndShouldThrow_Throws() + { + Expression> expr = x => x.ReadOnlyProperty; + var chain = expr.Body.GetExpressionChain().ToArray(); + var setter = new Reflection.CompiledPropertyChainSetter(chain); + var obj = new TestClass(); + + await Assert.That(() => setter.TrySetValue(obj, SampleValue)) + .Throws(); + } + + /// Verifies that a compiled setter returns false when a parent link resolves to null without throwing. + /// A representing the asynchronous operation. + [Test] + public async Task CompiledPropertyChainSetter_TrySetValue_WithNullParent_ReturnsFalse() + { + Expression> expr = x => x.Nested!.Property; + var chain = expr.Body.GetExpressionChain().ToArray(); + var setter = new Reflection.CompiledPropertyChainSetter(chain); + var obj = new TestClass { Nested = null }; + + var result = setter.TrySetValue(obj, SampleValue, false); + + await Assert.That(result).IsFalse(); + } + + /// Verifies that a compiled setter returns false for a null source when not configured to throw. + /// A representing the asynchronous operation. + [Test] + public async Task CompiledPropertyChainSetter_TrySetValue_WithNullSourceAndNoThrow_ReturnsFalse() + { + Expression> expr = x => x.Property; + var chain = expr.Body.GetExpressionChain().ToArray(); + var setter = new Reflection.CompiledPropertyChainSetter(chain); + + var result = setter.TrySetValue(null, SampleValue, false); + + await Assert.That(result).IsFalse(); + } + + /// Wraps a sequence as a lazily-yielded enumerable so it is neither an array nor an . + /// The source sequence. + /// A deferred enumerable producing the same items. + private static IEnumerable WrapAsEnumerable(IEnumerable source) + { + foreach (var item in source) + { + yield return item; + } + } + + /// A sample class exposing a multi-argument indexer for property-name tests. + public class MultiIndexerClass + { + /// Gets the value at the specified two-dimensional index. + /// The first index. + /// The second index. + /// The sum of the two indices. + public int this[int first, int second] => first + second; + } + + /// A sample class exposing an event for event-args reflection tests. + public class EventClass + { + /// An event used to exercise event-args type resolution. + public event EventHandler? SampleEvent; + + /// Raises the sample event. + public void Raise() => SampleEvent?.Invoke(this, EventArgs.Empty); + } + + /// A sample view model used to exercise . + public class SampleViewModel : ReactiveObject + { + /// Gets or sets the observed name value. + public string? Name + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + } + + /// A sample view used to exercise . + public class SampleView : ReactiveObject, IViewFor + { + /// Gets or sets the strongly-typed view model. + public SampleViewModel? ViewModel + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + + /// Gets or sets the weakly-typed view model. + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (SampleViewModel?)value; + } + } + /// A sample class used as the target of advanced reflection tests. public class TestClass { diff --git a/src/tests/ReactiveUI.Tests/Expressions/ReflectionTests.cs b/src/tests/ReactiveUI.Tests/Expressions/ReflectionTests.cs index 86c2061b4c..b83d80dcda 100644 --- a/src/tests/ReactiveUI.Tests/Expressions/ReflectionTests.cs +++ b/src/tests/ReactiveUI.Tests/Expressions/ReflectionTests.cs @@ -225,7 +225,7 @@ public void GetMemberInfo_WithUnsupportedExpression_Throws() const int ConstantValue = 42; var constant = System.Linq.Expressions.Expression.Constant(ConstantValue); - Assert.Throws(() => constant.GetMemberInfo()); + _ = Assert.Throws(() => constant.GetMemberInfo()); } /// Verifies that GetParent returns the object expression of an index expression. @@ -266,7 +266,7 @@ public void GetParent_WithUnsupportedExpression_Throws() const int ConstantValue = 42; var constant = System.Linq.Expressions.Expression.Constant(ConstantValue); - Assert.Throws(() => constant.GetParent()); + _ = Assert.Throws(() => constant.GetParent()); } /// Verifies that a value fetcher reads a value from a field. @@ -392,7 +392,7 @@ public async Task IsStatic_WithInstanceProperty_ReturnsFalse() public void IsStatic_WithNull_Throws() { PropertyInfo? propertyInfo = null; - Assert.Throws(() => propertyInfo!.IsStatic()); + _ = Assert.Throws(() => propertyInfo!.IsStatic()); } /// Verifies that IsStatic returns true for a static property. @@ -494,6 +494,47 @@ public async Task TrySetValueToPropertyChain_WithValidChain_SetsValue() await Assert.That(obj.Nested!.Property).IsEqualTo(SetValueText); } + /// An empty expression chain has no member to resolve and throws. + [Test] + public void TryGetValueForPropertyChain_EmptyChain_Throws() => + Assert.Throws(() => + Reflection.TryGetValueForPropertyChain(out _, new object(), [])); + + /// A null intermediate value in the chain yields a failed lookup. + /// A task representing the asynchronous operation. + [Test] + public async Task TryGetValueForPropertyChain_NullIntermediate_ReturnsFalse() + { + Expression> expr = x => x.Nested!.Property; + var chain = expr.Body.GetExpressionChain(); + + var result = Reflection.TryGetValueForPropertyChain(out var value, null, chain); + + using (Assert.Multiple()) + { + await Assert.That(result).IsFalse(); + await Assert.That(value).IsNull(); + } + } + + /// Setting a writable member through the chain assigns the value and returns true. + /// A task representing the asynchronous operation. + [Test] + public async Task TrySetValueToPropertyChain_WritableMember_SetsValue() + { + var target = new TestClass(); + Expression> expr = x => x.Property; + var chain = expr.Body.GetExpressionChain(); + + var result = Reflection.TrySetValueToPropertyChain(target, chain, "assigned", shouldThrow: false); + + using (Assert.Multiple()) + { + await Assert.That(result).IsTrue(); + await Assert.That(target.Property).IsEqualTo("assigned"); + } + } + /// A sample class used as the target of reflection tests. public class TestClass { diff --git a/src/tests/ReactiveUI.Tests/Infrastructure/RxAppTestExtensions.cs b/src/tests/ReactiveUI.Tests/Infrastructure/RxAppTestExtensions.cs index 25529ea9e2..f03ef89a9c 100644 --- a/src/tests/ReactiveUI.Tests/Infrastructure/RxAppTestExtensions.cs +++ b/src/tests/ReactiveUI.Tests/Infrastructure/RxAppTestExtensions.cs @@ -37,7 +37,7 @@ public static void ResetAndReinitialize() AppLocator.SetLocator(resolver); // Initialize ReactiveUI with core services - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithCoreServices().BuildApp(); } diff --git a/src/tests/ReactiveUI.Tests/Interactions/InteractionHandleObservableTests.cs b/src/tests/ReactiveUI.Tests/Interactions/InteractionHandleObservableTests.cs new file mode 100644 index 0000000000..bd6b6058db --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Interactions/InteractionHandleObservableTests.cs @@ -0,0 +1,185 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI.Tests.Utilities.Schedulers; + +namespace ReactiveUI.Tests.Interactions; + +/// Tests for the sequential interaction runner behind . +public class InteractionHandleObservableTests +{ + /// The output produced by handlers that succeed. + private const string ResultOutput = "result"; + + /// A handler that throws synchronously surfaces its exception to the observer. + /// A representing the asynchronous operation. + [Test] + public async Task HandlerThrowingSynchronouslyForwardsError() + { + var interaction = new Interaction(Sequencer.Immediate); + var boom = new InvalidOperationException("handler boom"); + _ = interaction.RegisterHandler((Action>)(_ => throw boom)); + + Exception? received = null; + using var subscription = interaction.Handle(RxVoid.Default).Subscribe(_ => { }, ex => received = ex); + + await Assert.That(received).IsSameReferenceAs(boom); + } + + /// An error from a handler's observable surfaces to the observer. + /// A representing the asynchronous operation. + [Test] + public async Task HandlerObservableErrorForwardsError() + { + var interaction = new Interaction(Sequencer.Immediate); + var boom = new InvalidOperationException("observable boom"); + _ = interaction.RegisterHandler(_ => new ThrowingObservable(boom)); + + Exception? received = null; + using var subscription = interaction.Handle(RxVoid.Default).Subscribe(_ => { }, ex => received = ex); + + await Assert.That(received).IsSameReferenceAs(boom); + } + + /// With a deferring scheduler the handler step is queued and only runs once the scheduler advances. + /// A representing the asynchronous operation. + [Test] + public async Task DeferredHandlerStepRunsWhenSchedulerStarts() + { + var scheduler = new VirtualTimeScheduler(); + var interaction = new Interaction(scheduler); + _ = interaction.RegisterHandler((Action>)(context => context.SetOutput(ResultOutput))); + + string? result = null; + using var subscription = interaction.Handle(RxVoid.Default).Subscribe(r => result = r); + + // The step is queued on the scheduler, not run inline. + await Assert.That(result).IsNull(); + + scheduler.Start(); + + await Assert.That(result).IsEqualTo(ResultOutput); + } + + /// Disposing before the deferred step runs cancels the run so no output is produced. + /// A representing the asynchronous operation. + [Test] + public async Task DisposingBeforeDeferredStepCancelsRun() + { + var scheduler = new VirtualTimeScheduler(); + var interaction = new Interaction(scheduler); + _ = interaction.RegisterHandler((Action>)(context => context.SetOutput(ResultOutput))); + + string? result = null; + var subscription = interaction.Handle(RxVoid.Default).Subscribe(r => result = r); + subscription.Dispose(); + + scheduler.Start(); + + await Assert.That(result).IsNull(); + } + + /// If a context reports handled but its output retrieval throws, the error is forwarded to the observer. + /// A representing the asynchronous operation. + [Test] + public async Task HandledContextWithThrowingOutputForwardsError() + { + var interaction = new ThrowingOutputInteraction(Sequencer.Immediate); + + Exception? received = null; + using var subscription = interaction.Handle(RxVoid.Default).Subscribe(_ => { }, ex => received = ex); + + await Assert.That(received).IsNotNull(); + } + + /// A step running after the sink has been disposed is a no-op: the observer receives nothing. + /// A representing the asynchronous operation. + [Test] + public async Task StepAfterDisposeIsIgnored() + { + var observer = new RecordingObserver(); + var context = new ThrowingOutputContext(RxVoid.Default); + var interaction = new Interaction(Sequencer.Immediate); + var sink = new InteractionHandleObservable.Sink( + observer, + [], + context, + Sequencer.Immediate, + interaction, + RxVoid.Default); + + sink.Dispose(); + sink.Step(0); + + using (Assert.Multiple()) + { + await Assert.That(observer.Values).IsEmpty(); + await Assert.That(observer.Error).IsNull(); + await Assert.That(observer.Completed).IsFalse(); + } + } + + /// An observable that errors any subscriber synchronously. + /// The error to deliver on subscribe. + private sealed class ThrowingObservable(Exception error) : IObservable + { + /// + public IDisposable Subscribe(IObserver observer) + { + observer.OnError(error); + return EmptyDisposable.Instance; + } + } + + /// An interaction whose context reports handled but throws when its output is read. + /// The handler scheduler. + private sealed class ThrowingOutputInteraction(ISequencer scheduler) : Interaction(scheduler) + { + /// + protected override IOutputContext GenerateContext(RxVoid input) => new ThrowingOutputContext(input); + } + + /// A context that always reports handled but throws when its output is retrieved. + /// The interaction input. + private sealed class ThrowingOutputContext(RxVoid input) : IOutputContext + { + /// + public RxVoid Input => input; + + /// + public bool IsHandled => true; + + /// + public void SetOutput(string output) + { + // Intentionally empty: this stub forces the handled-but-no-output path. + } + + /// + public string GetOutput() => throw new InvalidOperationException("output boom"); + } + + /// Records every notification an observer receives. + private sealed class RecordingObserver : IObserver + { + /// Gets the values delivered via . + public List Values { get; } = []; + + /// Gets the error delivered via , if any. + public Exception? Error { get; private set; } + + /// Gets a value indicating whether was invoked. + public bool Completed { get; private set; } + + /// + public void OnNext(string value) => Values.Add(value); + + /// + public void OnError(Exception error) => Error = error; + + /// + public void OnCompleted() => Completed = true; + } +} diff --git a/src/tests/ReactiveUI.Tests/InteractionsTest.cs b/src/tests/ReactiveUI.Tests/InteractionsTest.cs index c6a959b3b0..dbefe4df23 100644 --- a/src/tests/ReactiveUI.Tests/InteractionsTest.cs +++ b/src/tests/ReactiveUI.Tests/InteractionsTest.cs @@ -30,7 +30,7 @@ public async Task AttemptingToGetInteractionOutputBeforeItHasBeenSetShouldCauseE { var interaction = new Interaction(Sequencer.Immediate); - interaction.RegisterHandler(context => _ = ((InteractionContext)context).GetOutput()); + _ = interaction.RegisterHandler(context => _ = ((InteractionContext)context).GetOutput()); var ex = Assert.Throws(() => interaction.Handle(RxVoid.Default).Subscribe()); await Assert.That(ex.Message).IsEqualTo("Output has not been set."); @@ -43,7 +43,7 @@ public async Task AttemptingToSetInteractionOutputMoreThanOnceShouldCauseExcepti { var interaction = new Interaction(Sequencer.Immediate); - interaction.RegisterHandler(context => + _ = interaction.RegisterHandler(context => { context.SetOutput(RxVoid.Default); context.SetOutput(RxVoid.Default); @@ -59,7 +59,7 @@ public async Task AttemptingToSetInteractionOutputMoreThanOnceShouldCauseExcepti public async Task HandledInteractionsShouldNotCauseException() { var interaction = new Interaction(Sequencer.Immediate); - interaction.RegisterHandler(static c => c.SetOutput(true)); + _ = interaction.RegisterHandler(static c => c.SetOutput(true)); // Await rather than block: blocking (.Wait()) on a CurrentThreadScheduler-scheduled interaction can deadlock // when a scheduler trampoline is already active on the test thread. @@ -78,7 +78,7 @@ public async Task HandlersAreExecutedOnHandlerScheduler() using (interaction.RegisterHandler(x => x.SetOutput("done"))) { var handled = false; - interaction + _ = interaction .Handle(RxVoid.Default).Subscribe(_ => handled = true); // With ImmediateScheduler, handlers execute immediately @@ -107,19 +107,17 @@ public async Task HandlersCanContainAsynchronousCode() .Do(_ => x.SetOutput(OutputB))); using (handler1A) + using (handler1B) { - using (handler1B) - { - var result = interaction - .Handle(RxVoid.Default).Collect(); - - await Assert.That(result).IsEmpty(); - scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5)); - await Assert.That(result).IsEmpty(); - scheduler.AdvanceBy(TimeSpan.FromSeconds(0.6)); - await Assert.That(result).Count().IsEqualTo(1); - await Assert.That(result[0]).IsEqualTo(OutputB); - } + var result = interaction + .Handle(RxVoid.Default).Collect(); + + await Assert.That(result).IsEmpty(); + scheduler.AdvanceBy(TimeSpan.FromSeconds(0.5)); + await Assert.That(result).IsEmpty(); + scheduler.AdvanceBy(TimeSpan.FromSeconds(0.6)); + await Assert.That(result).Count().IsEqualTo(1); + await Assert.That(result[0]).IsEqualTo(OutputB); } await Assert.That(handler1AWasCalled).IsFalse(); @@ -132,7 +130,7 @@ public async Task HandlersCanContainAsynchronousCodeViaTasks() { var interaction = new Interaction(Sequencer.Immediate); - interaction.RegisterHandler(context => + _ = interaction.RegisterHandler(context => { context.SetOutput(ResultOutput); return Task.FromResult(true); @@ -170,12 +168,10 @@ public async Task HandlersCanOptNotToHandleTheInteraction() using (handler1B) { using (handler1C) + using (Assert.Multiple()) { - using (Assert.Multiple()) - { - await Assert.That(await interaction.Handle(false).FirstAsync()).IsEqualTo(OutputC); - await Assert.That(await interaction.Handle(true).FirstAsync()).IsEqualTo(OutputC); - } + await Assert.That(await interaction.Handle(false).FirstAsync()).IsEqualTo(OutputC); + await Assert.That(await interaction.Handle(true).FirstAsync()).IsEqualTo(OutputC); } using (Assert.Multiple()) @@ -240,10 +236,10 @@ public void RegisterNullHandlerShouldCauseException() { var interaction = new Interaction(Sequencer.Immediate); - Assert.Throws(() => + _ = Assert.Throws(() => interaction.RegisterHandler((Action>)null!)); - Assert.Throws(() => interaction.RegisterHandler(null!)); - Assert.Throws(() => + _ = Assert.Throws(() => interaction.RegisterHandler(null!)); + _ = Assert.Throws(() => interaction.RegisterHandler((Func, IObservable>)null!)); } @@ -261,8 +257,8 @@ public async Task UnhandledInteractionsShouldCauseException() await Assert.That(ex.Input).IsEqualTo("foo"); } - interaction.RegisterHandler(_ => { }); - interaction.RegisterHandler(_ => { }); + _ = interaction.RegisterHandler(_ => { }); + _ = interaction.RegisterHandler(_ => { }); ex = await Assert.That(() => interaction.Handle("bar").FirstAsync()) .Throws>(); using (Assert.Multiple()) diff --git a/src/tests/ReactiveUI.Tests/IroObservableForPropertyTest.cs b/src/tests/ReactiveUI.Tests/IroObservableForPropertyTest.cs index 8500a14514..58420358a6 100644 --- a/src/tests/ReactiveUI.Tests/IroObservableForPropertyTest.cs +++ b/src/tests/ReactiveUI.Tests/IroObservableForPropertyTest.cs @@ -85,7 +85,7 @@ public async Task GetNotificationForProperty_PropertyChanges_EmitsNotification() var expression = System.Linq.Expressions.Expression.Property(param, nameof(TestReactiveObject.TestProperty)); var changes = new List>(); - oaph.GetNotificationForProperty(sender, expression, nameof(TestReactiveObject.TestProperty)).ObserveOn(Sequencer.Immediate).Subscribe(changes.Add); + _ = oaph.GetNotificationForProperty(sender, expression, nameof(TestReactiveObject.TestProperty)).ObserveOn(Sequencer.Immediate).Subscribe(changes.Add); sender.TestProperty = "value1"; sender.TestProperty = "value2"; diff --git a/src/tests/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs b/src/tests/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs index 06bc01d32a..f1c431c2f2 100644 --- a/src/tests/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs +++ b/src/tests/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs @@ -49,7 +49,7 @@ public async Task Map_NullFactory_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - locator.Map(null!); + _ = locator.Map(null!); await Task.CompletedTask; }); } @@ -63,7 +63,7 @@ public async Task Map_OverwritesExistingMapping() var locator = new DefaultViewLocator(); var callCount = 0; - locator + _ = locator .Map(() => { callCount++; @@ -75,7 +75,7 @@ public async Task Map_OverwritesExistingMapping() return new(); }); - locator.ResolveView(); + _ = locator.ResolveView(); await Assert.That(callCount).IsEqualTo(OverwriteIncrement); } @@ -87,7 +87,7 @@ public async Task Map_RegistersViewFactory() { var locator = new DefaultViewLocator(); - locator.Map(() => new()); + _ = locator.Map(() => new()); var view = locator.ResolveView(); @@ -125,7 +125,7 @@ public async Task Map_WithContract_RegistersContractSpecificView() { var locator = new DefaultViewLocator(); - locator.Map(() => new(), MobileContract) + _ = locator.Map(() => new(), MobileContract) .Map(() => new(), "desktop"); var mobileView = locator.ResolveView(MobileContract); @@ -142,7 +142,7 @@ public async Task ResolveView_Generic_CreatesNewInstanceOnEachCall() { var locator = new DefaultViewLocator(); - locator.Map(() => new()); + _ = locator.Map(() => new()); var view1 = locator.ResolveView(); var view2 = locator.ResolveView(); @@ -165,7 +165,7 @@ public async Task ResolveView_Generic_ExplicitMappingTakesPriorityOverServiceLoc try { var locator = new DefaultViewLocator(); - locator.Map(() => new()); + _ = locator.Map(() => new()); var view = locator.ResolveView(); @@ -222,7 +222,7 @@ public async Task ResolveView_Generic_WithContract_UsesExplicitMapping() { var locator = new DefaultViewLocator(); - locator.Map(() => new(), MobileContract); + _ = locator.Map(() => new(), MobileContract); var view = locator.ResolveView(MobileContract); @@ -290,7 +290,7 @@ public async Task ResolveView_Instance_SetsViewModelProperty() var locator = new DefaultViewLocator(); var vm = new TestViewModel(); - locator.Map(() => new()); + _ = locator.Map(() => new()); var view = locator.ResolveView(vm); @@ -306,7 +306,7 @@ public async Task ResolveView_Instance_UsesExplicitMapping() var locator = new DefaultViewLocator(); var vm = new TestViewModel(); - locator.Map(() => new()); + _ = locator.Map(() => new()); var view = locator.ResolveView(vm); @@ -322,7 +322,7 @@ public async Task ResolveView_Instance_WithContract_UsesContractMapping() var locator = new DefaultViewLocator(); var vm = new TestViewModel(); - locator.Map(() => new(), MobileContract); + _ = locator.Map(() => new(), MobileContract); var view = locator.ResolveView(vm, MobileContract); @@ -337,7 +337,7 @@ public async Task ResolveView_Instance_WithContract_UsesContractMapping() public async Task ResolveView_ThreadSafe_ConcurrentResolvesDontThrow() { var locator = new DefaultViewLocator(); - locator.Map(() => new()); + _ = locator.Map(() => new()); var tasks = new List(); for (var i = 0; i < ConcurrentIterations; i++) @@ -360,7 +360,7 @@ public async Task Unmap_AllowsChaining() { var locator = new DefaultViewLocator(); - locator.Map(() => new(), "c1") + _ = locator.Map(() => new(), "c1") .Map(() => new(), "c2"); var result = locator.Unmap("c1") @@ -377,7 +377,7 @@ public async Task Unmap_NonExistentMapping_DoesNotThrow() { var locator = new DefaultViewLocator(); - locator.Unmap("nonexistent"); + _ = locator.Unmap("nonexistent"); await Assert.That(locator.ResolveView("nonexistent")).IsNull(); } @@ -389,11 +389,11 @@ public async Task Unmap_RemovesDefaultMapping() { var locator = new DefaultViewLocator(); - locator.Map(() => new()); + _ = locator.Map(() => new()); await Assert.That(locator.ResolveView()).IsNotNull(); - locator.Unmap(); + _ = locator.Unmap(); await Assert.That(locator.ResolveView()).IsNull(); } @@ -405,11 +405,11 @@ public async Task Unmap_RemovesMappingForContract() { var locator = new DefaultViewLocator(); - locator.Map(() => new(), MobileContract); + _ = locator.Map(() => new(), MobileContract); await Assert.That(locator.ResolveView(MobileContract)).IsNotNull(); - locator.Unmap(MobileContract); + _ = locator.Unmap(MobileContract); await Assert.That(locator.ResolveView(MobileContract)).IsNull(); } @@ -423,7 +423,7 @@ public async Task Unmap_ThreadSafe_ConcurrentUnmapsDontThrow() for (var i = 0; i < ConcurrentIterations; i++) { - locator.Map(() => new(), $"contract{i}"); + _ = locator.Map(() => new(), $"contract{i}"); } var tasks = new List(); diff --git a/src/tests/ReactiveUI.Tests/Locator/ViewLocatorTests.cs b/src/tests/ReactiveUI.Tests/Locator/ViewLocatorTests.cs index 9d9fdeaa10..d43ef37bd1 100644 --- a/src/tests/ReactiveUI.Tests/Locator/ViewLocatorTests.cs +++ b/src/tests/ReactiveUI.Tests/Locator/ViewLocatorTests.cs @@ -35,8 +35,8 @@ public async Task Current_CanBeConfigured_EndToEnd() AppBuilder.ResetBuilderStateForTests(); var builder = RxAppBuilder.CreateReactiveUIBuilder(); - builder.ConfigureViewLocator(locator => locator.Map(() => new())); - builder.WithCoreServices().BuildApp(); + _ = builder.ConfigureViewLocator(locator => locator.Map(() => new())); + _ = builder.WithCoreServices().BuildApp(); var current = ViewLocator.Current; var view = current.ResolveView(); @@ -85,7 +85,7 @@ public async Task Current_ReturnsNewInstance_AfterReinitialization() // Simulate re-initialization RxAppBuilder.ResetForTesting(); AppBuilder.ResetBuilderStateForTests(); - RxAppBuilder.CreateReactiveUIBuilder() + _ = RxAppBuilder.CreateReactiveUIBuilder() .WithCoreServices().BuildApp(); var current2 = ViewLocator.Current; diff --git a/src/tests/ReactiveUI.Tests/Locator/ViewMappingBuilderTests.cs b/src/tests/ReactiveUI.Tests/Locator/ViewMappingBuilderTests.cs index adfa3c4dbe..4c4a7f7da2 100644 --- a/src/tests/ReactiveUI.Tests/Locator/ViewMappingBuilderTests.cs +++ b/src/tests/ReactiveUI.Tests/Locator/ViewMappingBuilderTests.cs @@ -34,7 +34,7 @@ public async Task Map_WithParameterlessConstructor_ShouldRegisterAndResolveView( var builder = new ViewMappingBuilder(locator); // Act - builder.Map(); + _ = builder.Map(); var view = locator.ResolveView(); // Assert @@ -52,7 +52,7 @@ public async Task Map_WithParameterlessConstructor_ShouldSupportContracts() var builder = new ViewMappingBuilder(locator); // Act - builder + _ = builder .Map(Contract1) .Map(Contract2); @@ -90,7 +90,7 @@ public async Task Map_WithParameterlessConstructor_ShouldAllowChaining() var builder = new ViewMappingBuilder(locator); // Act - builder + _ = builder .Map() .Map(); @@ -127,7 +127,7 @@ public async Task Map_WithFactory_ShouldRegisterAndResolveView() var createdView = new TestView(); // Act - builder.Map(() => createdView); + _ = builder.Map(() => createdView); var view = locator.ResolveView(); // Assert @@ -146,7 +146,7 @@ public async Task Map_WithFactory_ShouldSupportContracts() var view2Instance = new TestView(); // Act - builder + _ = builder .Map(() => view1Instance, Contract1) .Map(() => view2Instance, Contract2); @@ -169,7 +169,7 @@ public async Task Map_WithFactory_ShouldCallFactoryOnEachResolve() var callCount = 0; // Act - builder.Map(() => + _ = builder.Map(() => { callCount++; return new(); @@ -216,7 +216,7 @@ public async Task MapFromServiceLocator_ShouldResolveViewFromServiceLocator() resolver.Register(() => viewInstance, typeof(TestView)); // Act - builder.MapFromServiceLocator(); + _ = builder.MapFromServiceLocator(); var view = locator.ResolveView(); // Assert @@ -241,7 +241,7 @@ public async Task MapFromServiceLocator_ShouldSupportContracts() resolver.Register(() => view2Instance, typeof(TestView)); // Act - builder + _ = builder .MapFromServiceLocator(Contract1) .MapFromServiceLocator(Contract2); @@ -264,7 +264,7 @@ public async Task MapFromServiceLocator_ShouldThrowInvalidOperationException_Whe var builder = new ViewMappingBuilder(locator); // Act - builder.MapFromServiceLocator(); + _ = builder.MapFromServiceLocator(); // Assert - Should throw when trying to resolve await Assert.That(() => locator.ResolveView()) @@ -308,7 +308,7 @@ public async Task MapFromServiceLocator_ShouldAllowChaining() resolver.Register(() => new AlternateView(), typeof(AlternateView)); // Act - builder + _ = builder .MapFromServiceLocator() .MapFromServiceLocator(); @@ -336,7 +336,7 @@ public async Task Builder_ShouldAllowMixedRegistrationTypes() resolver.Register(() => new AnotherView(), typeof(AnotherView)); // Act - Mix all three registration types - builder + _ = builder .Map() // Parameterless constructor .Map(() => factoryView) // Factory .MapFromServiceLocator(); // Service locator diff --git a/src/tests/ReactiveUI.Tests/MessageBus/MessageBusTest.cs b/src/tests/ReactiveUI.Tests/MessageBus/MessageBusTest.cs index 5134545fa0..589383c4e5 100644 --- a/src/tests/ReactiveUI.Tests/MessageBus/MessageBusTest.cs +++ b/src/tests/ReactiveUI.Tests/MessageBus/MessageBusTest.cs @@ -85,7 +85,7 @@ public async Task IsRegistered_AfterListen_ReturnsTrue() { var messageBus = new MessageBusType(); - messageBus.Listen().Subscribe(); + _ = messageBus.Listen().Subscribe(); await Assert.That(messageBus.IsRegistered(typeof(int))).IsTrue(); } @@ -168,7 +168,7 @@ public async Task Listen_ColdObservable_NoSideEffects() await Assert.That(messageBus.IsRegistered(typeof(string))).IsFalse(); - observable.Subscribe(); + _ = observable.Subscribe(); await Assert.That(messageBus.IsRegistered(typeof(string))).IsTrue(); } @@ -348,7 +348,7 @@ public async Task RegisterMessageSource_NullSource_ThrowsArgumentNullException() await Assert.ThrowsAsync(async () => { - messageBus.RegisterMessageSource(null!); + _ = messageBus.RegisterMessageSource(null!); await Task.CompletedTask; }); } @@ -365,7 +365,7 @@ public async Task RegisterMessageSource_ObservableComplete_UnsubscribesCorrectly var source = new Signal(); var messages = messageBus.Listen().Collect(); - messageBus.RegisterMessageSource(source); + _ = messageBus.RegisterMessageSource(source); source.OnNext(BeforeMessage); source.OnCompleted(); @@ -393,7 +393,7 @@ public async Task RegisterMessageSource_ObservableError_DoesNotBreakBus() var source = new Signal(); var messages = messageBus.Listen().Collect(); - messageBus.RegisterMessageSource(source); + _ = messageBus.RegisterMessageSource(source); source.OnNext(BeforeMessage); source.OnError(new InvalidOperationException("Test error")); @@ -421,7 +421,7 @@ public async Task RegisterMessageSource_SendsMessagesFromObservable() var source = new Signal(); var messages = messageBus.Listen().Collect(); - messageBus.RegisterMessageSource(source); + _ = messageBus.RegisterMessageSource(source); source.OnNext(FirstMessage); source.OnNext(SecondTextMessage); @@ -454,7 +454,7 @@ public async Task RegisterMessageSource_WithContract_SendsToCorrectListeners() const int SecondMessage = 2; const int ExpectedCount = 2; - messageBus.RegisterMessageSource(source, "MyContract"); + _ = messageBus.RegisterMessageSource(source, "MyContract"); source.OnNext(1); source.OnNext(SecondMessage); @@ -745,7 +745,7 @@ public async Task SendMessage_NullableValueType_WorksCorrectly() var messageBus = new MessageBusType(); messageBus.RegisterScheduler(Sequencer.Immediate); var messages = new List(); - messageBus.Listen().Subscribe(messages.Add); + _ = messageBus.Listen().Subscribe(messages.Add); const int ExpectedCount = 3; const int ThirdIndex = 2; @@ -771,7 +771,7 @@ public async Task SendMessage_NullReferenceType_WorksCorrectly() var messageBus = new MessageBusType(); messageBus.RegisterScheduler(Sequencer.Immediate); var messages = new List(); - messageBus.Listen().Subscribe(messages.Add); + _ = messageBus.Listen().Subscribe(messages.Add); messageBus.SendMessage(HelloMessage); messageBus.SendMessage(null); diff --git a/src/tests/ReactiveUI.Tests/MessageBus/SkipFirstObserverTests.cs b/src/tests/ReactiveUI.Tests/MessageBus/SkipFirstObserverTests.cs new file mode 100644 index 0000000000..d28f625718 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/MessageBus/SkipFirstObserverTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Tests.MessageBus; + +/// Tests for . +public class SkipFirstObserverTests +{ + /// The first value is dropped and every subsequent value is forwarded. + /// A task representing the asynchronous operation. + [Test] + public async Task SkipsFirstValueThenForwards() + { + const int first = 1; + const int second = 2; + const int third = 3; + var downstream = new CapturingObserver(); + var observer = new SkipFirstObserver(downstream); + + observer.OnNext(first); + observer.OnNext(second); + observer.OnNext(third); + + await Assert.That(downstream.Values).IsEquivalentTo([second, third]); + } + + /// Errors and completion are forwarded to the downstream observer. + /// A task representing the asynchronous operation. + [Test] + public async Task ForwardsErrorAndCompletion() + { + var error = new InvalidOperationException("boom"); + + var erroring = new CapturingObserver(); + new SkipFirstObserver(erroring).OnError(error); + await Assert.That(erroring.Error).IsSameReferenceAs(error); + + var completing = new CapturingObserver(); + new SkipFirstObserver(completing).OnCompleted(); + await Assert.That(completing.Completed).IsTrue(); + } + + /// An observer that records the values, error and completion it receives. + /// The value type. + private sealed class CapturingObserver : IObserver + { + /// Gets the values that were forwarded. + public List Values { get; } = []; + + /// Gets the error that was forwarded, if any. + public Exception? Error { get; private set; } + + /// Gets a value indicating whether completion was forwarded. + public bool Completed { get; private set; } + + /// + public void OnNext(T value) => Values.Add(value); + + /// + public void OnError(Exception error) => Error = error; + + /// + public void OnCompleted() => Completed = true; + } +} diff --git a/src/tests/ReactiveUI.Tests/Mixins/DependencyResolverMixinsTypeFactoryTests.cs b/src/tests/ReactiveUI.Tests/Mixins/DependencyResolverMixinsTypeFactoryTests.cs new file mode 100644 index 0000000000..e42162c321 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Mixins/DependencyResolverMixinsTypeFactoryTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Reflection; + +namespace ReactiveUI.Tests.Mixins; + +/// Tests for the internal type factory. +public class DependencyResolverMixinsTypeFactoryTests +{ + /// A type with a public parameterless constructor yields a factory that creates an instance. + /// A representing the asynchronous operation. + [Test] + public async Task TypeFactoryCreatesInstanceForParameterlessType() + { + var factory = DependencyResolverMixins.TypeFactory(typeof(ParameterlessType).GetTypeInfo()); + + var instance = factory(); + + using (Assert.Multiple()) + { + await Assert.That(instance).IsTypeOf(); + await Assert.That(((ParameterlessType)instance).Initialized).IsTrue(); + } + } + + /// A type lacking a public parameterless constructor throws when its factory is built. + /// A representing the asynchronous operation. + [Test] + public async Task TypeFactoryThrowsForTypeMissingParameterlessConstructor() + { + await Assert.That(() => DependencyResolverMixins.TypeFactory(typeof(NoParameterlessType).GetTypeInfo())) + .Throws(); + } + + /// A type with a public parameterless constructor. + private sealed class ParameterlessType + { + /// Gets a value indicating whether the instance was constructed. + public bool Initialized { get; } = true; + } + + /// A type whose only constructor requires an argument. + /// An unused required argument. + private sealed class NoParameterlessType(int value) + { + /// Gets the captured value. + public int Value { get; } = value; + } +} diff --git a/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAotExtensionsTests.cs b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAotExtensionsTests.cs index 7b32f86610..f9b32fa306 100644 --- a/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAotExtensionsTests.cs +++ b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverAotExtensionsTests.cs @@ -18,7 +18,7 @@ public async Task RegisterSingletonViewForViewModelAOT_RegistersSingletonView() { using var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModelAOT(); + _ = resolver.RegisterSingletonViewForViewModelAOT(); var view1 = resolver.GetService>(); var view2 = resolver.GetService>(); @@ -63,7 +63,7 @@ public async Task RegisterSingletonViewForViewModelAOT_WithContract_RegistersVie { using var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModelAOT("SingletonContract"); + _ = resolver.RegisterSingletonViewForViewModelAOT("SingletonContract"); var view1 = resolver.GetService>("SingletonContract"); var view2 = resolver.GetService>("SingletonContract"); @@ -83,7 +83,7 @@ public async Task RegisterViewForViewModelAOT_RegistersTransientView() { using var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModelAOT(); + _ = resolver.RegisterViewForViewModelAOT(); var view1 = resolver.GetService>(); var view2 = resolver.GetService>(); @@ -128,7 +128,7 @@ public async Task RegisterViewForViewModelAOT_WithContract_RegistersViewWithCont { using var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModelAOT("MyContract"); + _ = resolver.RegisterViewForViewModelAOT("MyContract"); var view = resolver.GetService>("MyContract"); diff --git a/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs index cd998c68ad..43477369dd 100644 --- a/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs +++ b/src/tests/ReactiveUI.Tests/Mixins/MutableDependencyResolverExtensionsTests.cs @@ -24,7 +24,7 @@ public class MutableDependencyResolverExtensionsTests public async Task RegisterSingletonViewForViewModelRegistersSingleton() { var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModel(); + _ = resolver.RegisterSingletonViewForViewModel(); var view = resolver.GetService>(); @@ -52,7 +52,7 @@ public async Task RegisterSingletonViewForViewModelReturnsResolver() public async Task RegisterSingletonViewForViewModelReturnsSameInstance() { var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModel(); + _ = resolver.RegisterSingletonViewForViewModel(); var view1 = resolver.GetService>(); var view2 = resolver.GetService>(); @@ -71,7 +71,7 @@ public async Task RegisterSingletonViewForViewModelReturnsSameInstance() public async Task RegisterSingletonViewForViewModelSupportsChaining() { var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModel().RegisterSingletonViewForViewModel(); + _ = resolver.RegisterSingletonViewForViewModel().RegisterSingletonViewForViewModel(); var view1 = resolver.GetService>(); var view2 = resolver.GetService>(); @@ -88,7 +88,7 @@ public async Task RegisterSingletonViewForViewModelSupportsChaining() public void RegisterSingletonViewForViewModelThrowsOnNullResolver() { IMutableDependencyResolver? resolver = null; - Assert.Throws(() => resolver!.RegisterSingletonViewForViewModel()); + _ = Assert.Throws(() => resolver!.RegisterSingletonViewForViewModel()); } /// Verifies that RegisterSingletonViewForViewModel registers a singleton view with a contract. @@ -97,7 +97,7 @@ public void RegisterSingletonViewForViewModelThrowsOnNullResolver() public async Task RegisterSingletonViewForViewModelWithContractRegistersSingleton() { var resolver = new ModernDependencyResolver(); - resolver.RegisterSingletonViewForViewModel(TestContract); + _ = resolver.RegisterSingletonViewForViewModel(TestContract); var view = resolver.GetService>(TestContract); @@ -114,7 +114,7 @@ public async Task RegisterSingletonViewForViewModelWithContractRegistersSingleto public async Task RegisterViewForViewModelCreatesNewInstanceEachTime() { var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModel(); + _ = resolver.RegisterViewForViewModel(); var view1 = resolver.GetService>(); var view2 = resolver.GetService>(); @@ -133,7 +133,7 @@ public async Task RegisterViewForViewModelCreatesNewInstanceEachTime() public async Task RegisterViewForViewModelRegistersView() { var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModel(); + _ = resolver.RegisterViewForViewModel(); var view = resolver.GetService>(); @@ -161,7 +161,7 @@ public async Task RegisterViewForViewModelReturnsResolver() public async Task RegisterViewForViewModelSupportsChaining() { var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModel().RegisterViewForViewModel(); + _ = resolver.RegisterViewForViewModel().RegisterViewForViewModel(); var view1 = resolver.GetService>(); var view2 = resolver.GetService>(); @@ -178,7 +178,7 @@ public async Task RegisterViewForViewModelSupportsChaining() public void RegisterViewForViewModelThrowsOnNullResolver() { IMutableDependencyResolver? resolver = null; - Assert.Throws(() => resolver!.RegisterViewForViewModel()); + _ = Assert.Throws(() => resolver!.RegisterViewForViewModel()); } /// Verifies that RegisterViewForViewModel registers a view with a contract. @@ -187,7 +187,7 @@ public void RegisterViewForViewModelThrowsOnNullResolver() public async Task RegisterViewForViewModelWithContractRegistersView() { var resolver = new ModernDependencyResolver(); - resolver.RegisterViewForViewModel(TestContract); + _ = resolver.RegisterViewForViewModel(TestContract); var view = resolver.GetService>(TestContract); diff --git a/src/tests/ReactiveUI.Tests/Mixins/ObservableLoggingMixinTests.cs b/src/tests/ReactiveUI.Tests/Mixins/ObservableLoggingMixinTests.cs index c29fb7a5f1..32c05c44eb 100644 --- a/src/tests/ReactiveUI.Tests/Mixins/ObservableLoggingMixinTests.cs +++ b/src/tests/ReactiveUI.Tests/Mixins/ObservableLoggingMixinTests.cs @@ -51,7 +51,7 @@ public async Task LocalInterfaceVariable_WorksWithIEnableLogger() () => logger.Log().Info(CultureInfo.InvariantCulture, OnCompletedFormat, Message)); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(1); subject.OnNext(SecondValue); @@ -78,7 +78,7 @@ public async Task Log_CompletesNormally_WithoutErrors() var results = new List(); var completed = false; - logged.ObserveOn(Sequencer.Immediate).Subscribe(results.Add, () => completed = true); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(results.Add, () => completed = true); await Assert.That(results).IsEquivalentTo(Enumerable.Range(1, RangeCount).ToList()); await Assert.That(completed).IsTrue(); @@ -99,7 +99,7 @@ public async Task Log_LogsErrors() var logged = subject.Log(logger, TestMessage); var errorCaught = false; - logged.ObserveOn(Sequencer.Immediate).Subscribe(_ => { }, _ => errorCaught = true); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(_ => { }, _ => errorCaught = true); subject.OnError(new InvalidOperationException("Test error")); @@ -123,7 +123,7 @@ public async Task Log_LogsOnNextOnErrorAndOnCompleted() var logged = subject.Log(logger, TestMessage); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(1); subject.OnNext(SecondValue); @@ -148,7 +148,7 @@ public async Task Log_WithNullMessage_UsesEmptyString() var logged = subject.Log(logger); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(1); subject.OnCompleted(); @@ -173,7 +173,7 @@ public async Task Log_WithStringifier_UsesCustomConversion() var logged = subject.Log(logger, TestMessage, x => $"Value: {x}"); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(ExpectedValue); subject.OnCompleted(); @@ -200,7 +200,7 @@ public async Task LogExtensionMethod_WorksWithTestEnableLogger() var logged = subject.Log(logger, TestMessage); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(1); subject.OnNext(SecondValue); @@ -226,7 +226,7 @@ public async Task LoggedCatch_CatchesExceptionAndReturnsNext() var caught = subject.LoggedCatch(logger, fallback, "Error occurred"); var values = new List(); - caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnError(new InvalidOperationException()); @@ -256,7 +256,7 @@ public async Task LoggedCatch_WithExceptionFunc_PassesExceptionToFactory() }); var values = new List(); - caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); var thrownException = new InvalidOperationException(TestMessage); subject.OnError(thrownException); @@ -283,7 +283,7 @@ public async Task LoggedCatch_WithExceptionType_CatchesSpecificException() "Specific error"); var values = new List(); - caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnError(new InvalidOperationException()); @@ -305,7 +305,7 @@ public async Task LoggedCatch_WithNullMessage_UsesEmptyString() var caught = subject.LoggedCatch(logger, Signal.Emit(1)); var values = new List(); - caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = caught.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnError(new InvalidOperationException()); @@ -326,7 +326,7 @@ public async Task LoggedCatch_WithNullNext_UsesDefault() var caught = subject.LoggedCatch(logger, null, "Error"); - caught.ObserveOn(Sequencer.Immediate).Subscribe(_ => { }, _ => { }, () => { }); + _ = caught.ObserveOn(Sequencer.Immediate).Subscribe(_ => { }, _ => { }, () => { }); subject.OnError(new InvalidOperationException()); @@ -355,7 +355,7 @@ public async Task ManualInline_WorksWithTestEnableLogger() () => logger.Log().Info(CultureInfo.InvariantCulture, OnCompletedFormat, Message)); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(1); subject.OnNext(SecondValue); @@ -381,7 +381,7 @@ public async Task NonGenericHelper_WorksWithIEnableLogger() var logged = LogNonGeneric(subject, logger, TestMessage); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(1); subject.OnNext(SecondValue); @@ -411,7 +411,7 @@ public async Task TestEnableLogger_CapturesGenericInfoWithTwoArguments() () => logger.Log().Info(CultureInfo.InvariantCulture, OnCompletedFormat, TestMessage)); var values = new List(); - logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); + _ = logged.ObserveOn(Sequencer.Immediate).Subscribe(values.Add); subject.OnNext(1); subject.OnNext(SecondValue); diff --git a/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs index e2267c1a02..854580ec0b 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/FakeCollectionViewModel.cs @@ -21,7 +21,7 @@ public FakeCollectionViewModel(FakeCollectionModel model) { Model = model; - this.WhenAny(static x => x.Model.SomeNumber, static x => x.Value.ToString()).ToProperty( + _ = this.WhenAny(static x => x.Model.SomeNumber, static x => x.Value.ToString()).ToProperty( this, static x => x.NumberAsString, out _numberAsString); diff --git a/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs b/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs index 634e3a5596..c6ebde5491 100644 --- a/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs +++ b/src/tests/ReactiveUI.Tests/Mocks/FooViewModel.cs @@ -19,7 +19,7 @@ public FooViewModel(Foo foo) // The per-set latency is a scheduler-native DelaySubscription (not a delay inside the awaited task): virtual-time // tests drive RxSchedulers.TaskpoolScheduler deterministically, whereas a thread-pool-bridged Task delay races them. - this.WhenAnyValue(static x => x.Setpoint) + _ = this.WhenAnyValue(static x => x.Setpoint) .SelectMany(value => Signal.FromAsync(() => foo.SetValueAsync(value)) .DelaySubscription(TimeSpan.FromMilliseconds(SetValueDelayMilliseconds), RxSchedulers.TaskpoolScheduler)) .Subscribe(); diff --git a/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs b/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs index 99bbdd6e0a..3cf2c43212 100644 --- a/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs +++ b/src/tests/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs @@ -49,7 +49,7 @@ public async Task NullableTypesTestShouldNotNeedDecorators2_ToProperty() { const int ExpectedAccountsFound = 3; var fixture = new WhenAnyTestFixture(); - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( static x => x.ProjectService.ProjectsNullable, static x => x.AccountService.AccountUsersNullable).Where(static tuple => tuple.Value1.Count > 0 && tuple.Value2.Count > 0).Select(static tuple => { @@ -270,7 +270,7 @@ public async Task OaphDeferSubscriptionWithInitialFuncValueShouldNotEmitInitialV await Assert.That(fixture.IsSubscribed).IsFalse(); int? emittedValue = null; - fixture.Source.Subscribe(val => emittedValue = val); + _ = fixture.Source.Subscribe(val => emittedValue = val); using (Assert.Multiple()) { @@ -325,7 +325,7 @@ public async Task OaphDeferSubscriptionWithInitialValueShouldNotEmitInitialValue await Assert.That(fixture.IsSubscribed).IsFalse(); int? emittedValue = null; - fixture.Source.Subscribe(val => emittedValue = val); + _ = fixture.Source.Subscribe(val => emittedValue = val); using (Assert.Multiple()) { @@ -348,7 +348,7 @@ public async Task OaphInitialValueShouldEmitInitialValue(int initialValue) await Assert.That(fixture.IsSubscribed).IsTrue(); int? emittedValue = null; - fixture.Source.Subscribe(val => emittedValue = val); + _ = fixture.Source.Subscribe(val => emittedValue = val); await Assert.That(emittedValue).IsEqualTo(initialValue); } @@ -452,7 +452,7 @@ public async Task OaphShouldRethrowErrors() await Assert.That(fixture.Value).IsEqualTo(InitialValue); new[] { 1, SecondInput, ThirdInput, ExpectedLastValue }.Run(input.OnNext); - fixture.ThrownExceptions.Subscribe(errors.Add); + _ = fixture.ThrownExceptions.Subscribe(errors.Add); // ImmediateScheduler executes synchronously, no need for scheduler.Start() await Assert.That(fixture.Value).IsEqualTo(ExpectedLastValue); @@ -548,7 +548,7 @@ public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName1() var scheduler = TestContext.Current!.GetScheduler(); var propertiesChanged = new List(); - Assert.Throws(() => + _ = Assert.Throws(() => { var fixture = new OaphIndexerTestFixture( 1, @@ -575,7 +575,7 @@ public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName2() { const int InvalidPathMode = 2; var scheduler = TestContext.Current!.GetScheduler(); - Assert.Throws(() => _ = new OaphIndexerTestFixture(InvalidPathMode, scheduler)); + _ = Assert.Throws(() => _ = new OaphIndexerTestFixture(InvalidPathMode, scheduler)); } /// ToProperty(nameof) should raise standard notifications. @@ -722,4 +722,234 @@ public async Task ToPropertyShouldFireBothChangingAndChanged() await Assert.That(resultChanged[1].Value).IsEqualTo("Baz"); } } + + /// The two-argument constructor seeds the default value. + /// A task representing the asynchronous operation. + [Test] + public async Task TwoArgConstructorUsesDefaultValue() + { + using var fixture = new ObservableAsPropertyHelper(Signal.Silent(), static _ => { }); + await Assert.That(fixture.Value).IsEqualTo(0); + } + + /// The value-plus-defer-plus-scheduler constructor is usable. + /// A task representing the asynchronous operation. + [Test] + public async Task InitialValueDeferSchedulerConstructor() + { + const int seed = 7; + using var fixture = new ObservableAsPropertyHelper(Signal.Silent(), static _ => { }, seed, false, Sequencer.Immediate); + await Assert.That(fixture.Value).IsEqualTo(seed); + } + + /// The onChanging-only constructor seeds the default value via the default factory. + /// A task representing the asynchronous operation. + [Test] + public async Task OnChangingOnlyConstructorUsesDefaultValue() + { + using var fixture = new ObservableAsPropertyHelper(Signal.Silent(), static _ => { }, static _ => { }); + await Assert.That(fixture.Value).IsEqualTo(0); + } + + /// The onChanging-plus-initial-value constructor seeds the supplied value. + /// A task representing the asynchronous operation. + [Test] + public async Task OnChangingInitialValueConstructor() + { + const int seed = 11; + using var fixture = new ObservableAsPropertyHelper(Signal.Silent(), static _ => { }, static _ => { }, seed); + await Assert.That(fixture.Value).IsEqualTo(seed); + } + + /// The onChanging-plus-initial-value-plus-defer constructor seeds the supplied value. + /// A task representing the asynchronous operation. + [Test] + public async Task OnChangingInitialValueDeferConstructor() + { + const int seed = 13; + using var fixture = new ObservableAsPropertyHelper(Signal.Silent(), static _ => { }, static _ => { }, seed, false); + await Assert.That(fixture.Value).IsEqualTo(seed); + } + + /// The Func-based initial value constructor evaluates the factory. + /// A task representing the asynchronous operation. + [Test] + public async Task FuncInitialValueConstructor() + { + const int seed = 17; + using var fixture = new ObservableAsPropertyHelper(Signal.Silent(), static _ => { }, static _ => { }, static () => seed, false); + await Assert.That(fixture.Value).IsEqualTo(seed); + } + + /// The default factory methods build usable helpers. + /// A task representing the asynchronous operation. + [Test] + public async Task DefaultFactoryMethods() + { + const int valueSeed = 3; + const int schedulerSeed = 5; + using var withoutValue = ObservableAsPropertyHelper.Default(); + using var withValue = ObservableAsPropertyHelper.Default(valueSeed); + using var withValueAndScheduler = ObservableAsPropertyHelper.Default(schedulerSeed, Sequencer.Immediate); + await Assert.That(withoutValue.Value).IsEqualTo(0); + await Assert.That(withValue.Value).IsEqualTo(valueSeed); + await Assert.That(withValueAndScheduler.Value).IsEqualTo(schedulerSeed); + } + + /// Disposing twice is a no-op. + /// A task representing the asynchronous operation. + [Test] + public async Task DisposeIsIdempotent() + { + var fixture = new ObservableAsPropertyHelper(Signal.Silent(), static _ => { }, 1); + fixture.Dispose(); + fixture.Dispose(); + await Assert.That(fixture.IsSubscribed).IsTrue(); + } + + /// A deferred source error is routed to a ThrownExceptions subscriber. + /// A task representing the asynchronous operation. + [Test] + public async Task ThrownExceptionsDeliversToSubscriber() + { + var error = new InvalidOperationException("boom"); + + // Defer subscription so the source (which errors on subscribe) is only touched once we read Value, + // by which point our ThrownExceptions observer is attached. + using var fixture = new ObservableAsPropertyHelper(new ErrorObservable(error), static _ => { }, 0, true, Sequencer.Immediate); + + Exception? received = null; + var subscription = fixture.ThrownExceptions.Subscribe(ex => received = ex); + _ = fixture.Value; + + await Assert.That(received).IsSameReferenceAs(error); + subscription.Dispose(); + } + + /// The constructor overload with an initial-value factory seeds the value and delivers later source values. + /// A representing the asynchronous operation. + [Test] + public async Task InitialValueFactoryConstructorSeedsAndDelivers() + { + const int Seed = 7; + var source = new Signal(); + + using var fixture = new ObservableAsPropertyHelper(source, static _ => { }, onChanging: null, getInitialValue: () => Seed); + + await Assert.That(fixture.Value).IsEqualTo(Seed); + + source.OnNext(EmittedValue); + await Assert.That(fixture.Value).IsEqualTo(EmittedValue); + } + + /// A null initial-value factory falls back to default(T) for the seeded value. + /// A representing the asynchronous operation. + [Test] + public async Task NullInitialValueFactoryFallsBackToDefault() + { + var source = new Signal(); + + using var fixture = new ObservableAsPropertyHelper(source, static _ => { }, onChanging: null, getInitialValue: null); + + await Assert.That(fixture.Value).IsEqualTo(0); + } + + /// When there is no thrown-exceptions subscriber, a source error is routed to the default exception handler. + /// A representing the asynchronous operation. + [Test] + [NotInParallel] + public async Task SourceErrorWithoutSubscriberGoesToDefaultExceptionHandler() + { + RxState.ResetForTesting(); + var handler = new CapturingExceptionObserver(); + RxState.InitializeExceptionHandler(handler); + try + { + var error = new InvalidOperationException("Die!"); + using var fixture = new ObservableAsPropertyHelper(new ErrorObservable(error), static _ => { }, 0, true, Sequencer.Immediate); + + _ = fixture.Value; + + await Assert.That(handler.Captured).IsSameReferenceAs(error); + } + finally + { + RxState.ResetForTesting(); + } + } + + /// A source value arriving after disposal is ignored rather than delivered. + /// A representing the asynchronous operation. + [Test] + public async Task OnSourceNextAfterDisposeIsIgnored() + { + var source = new Signal(); + var observed = new List(); + var fixture = new ObservableAsPropertyHelper(source, observed.Add, 0, false, Sequencer.Immediate); + observed.Clear(); + + fixture.Dispose(); + fixture.OnSourceNext(EmittedValue); + + await Assert.That(observed).IsEmpty(); + } + + /// Activating after disposal subscribes then immediately disposes the source subscription. + /// A representing the asynchronous operation. + [Test] + public async Task ActivateOnFirstAccessAfterDisposeDisposesSubscription() + { + var source = new TrackingObservable(); + var fixture = new ObservableAsPropertyHelper(source, static _ => { }, 0, true, Sequencer.Immediate); + fixture.Dispose(); + + fixture.ActivateOnFirstAccess(); + + await Assert.That(source.LastSubscriptionDisposed).IsTrue(); + } + + /// An exception observer that records the most recent exception it receives. + private sealed class CapturingExceptionObserver : IObserver + { + /// Gets the most recently captured exception. + public Exception? Captured { get; private set; } + + /// + public void OnNext(Exception value) => Captured = value; + + /// + public void OnError(Exception error) + { + } + + /// + public void OnCompleted() + { + } + } + + /// An observable that records whether the subscription it handed out has been disposed. + private sealed class TrackingObservable : IObservable + { + /// Gets a value indicating whether the most recent subscription was disposed. + public bool LastSubscriptionDisposed { get; private set; } + + /// + public IDisposable Subscribe(IObserver observer) => Scope.Create(() => LastSubscriptionDisposed = true); + } + + /// An observable that immediately errors any subscriber. + /// The element type. + /// The error to deliver on subscribe. + private sealed class ErrorObservable(Exception error) : IObservable + { + /// Errors the observer immediately. + /// The observer. + /// An empty disposable. + public IDisposable Subscribe(IObserver observer) + { + observer.OnError(error); + return EmptyDisposable.Instance; + } + } } diff --git a/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs b/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs index dfe67d1f8c..0c9d1b707a 100644 --- a/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs +++ b/src/tests/ReactiveUI.Tests/ObservableMixinsTest.cs @@ -21,7 +21,7 @@ public async Task WhereNotNull_EmitsAllNonNullValues() const int ExpectedCount = 3; const int ThirdIndex = 2; - ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); + _ = ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); subject.OnNext(1); subject.OnNext(SecondValue); @@ -41,7 +41,7 @@ public async Task WhereNotNull_FiltersNullValues() var subject = new Signal(); var results = new List(); - ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); + _ = ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); subject.OnNext("value1"); subject.OnNext(null); @@ -65,7 +65,7 @@ public async Task WhereNotNull_WithOnlyNulls_EmitsNothing() var subject = new Signal(); var results = new List(); - ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); + _ = ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); subject.OnNext(null); subject.OnNext(null); @@ -84,7 +84,7 @@ public async Task WhereNotNull_WorksWithReferenceTypes() var obj1 = new TestClass { Value = "test1" }; var obj2 = new TestClass { Value = "test2" }; - ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); + _ = ObservableMixins.WhereNotNull(subject).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); subject.OnNext(obj1); subject.OnNext(null); diff --git a/src/tests/ReactiveUI.Tests/ObservedChanged/NewGameViewModelTests.cs b/src/tests/ReactiveUI.Tests/ObservedChanged/NewGameViewModelTests.cs index dc6af8271b..35f523f144 100644 --- a/src/tests/ReactiveUI.Tests/ObservedChanged/NewGameViewModelTests.cs +++ b/src/tests/ReactiveUI.Tests/ObservedChanged/NewGameViewModelTests.cs @@ -25,7 +25,7 @@ public async Task CanAddUpToSevenPlayers() foreach (var i in Enumerable.Range(1, MaxPlayers)) { _viewmodel.NewPlayerName = "Player" + i; - _viewmodel.AddPlayer.Execute().Subscribe(); + _ = _viewmodel.AddPlayer.Execute().Subscribe(); await Assert.That(_viewmodel.Players).Count().IsEqualTo(i); } } diff --git a/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs b/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs index 45e6e57949..9f8c53a7a3 100644 --- a/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs +++ b/src/tests/ReactiveUI.Tests/ObservedChanged/ObservedChangedMixinTest.cs @@ -24,7 +24,7 @@ public async Task BindToIsNotFooledByIntermediateObjectSwitching() var input = new Signal(); var fixture = new HostTestFixture { Child = new() }; - input.BindTo(fixture, static x => x.Child!.IsNotNullString); + _ = input.BindTo(fixture, static x => x.Child!.IsNotNullString); await Assert.That(fixture.Child.IsNotNullString).IsNull(); @@ -53,7 +53,7 @@ public async Task BindToSmokeTest() var input = new Signal(); var fixture = new HostTestFixture { Child = new() }; - input.BindTo(fixture, static x => x.Child!.IsNotNullString); + _ = input.BindTo(fixture, static x => x.Child!.IsNotNullString); await Assert.That(fixture.Child.IsNotNullString).IsNull(); @@ -83,7 +83,7 @@ public void BindToStackOverFlowTest() var source = new BehaviorSignal>([]); - source.BindTo(fixtureA, static x => x.StackOverflowTrigger); + _ = source.BindTo(fixtureA, static x => x.StackOverflowTrigger); } /// Tests to make sure that Disposing disconnects BindTo and updates are no longer pushed. @@ -195,7 +195,7 @@ public async Task GetValueShouldActuallyReturnTheValue() var fixture = new TestFixture(); // ...whereas ObservableForProperty *is* guaranteed to. - ObservableMixins.WhereNotNull(fixture.ObservableForProperty(x => x.IsOnlyOneWord).Select(x => x.GetValue())).Subscribe(output.Add); + _ = ObservableMixins.WhereNotNull(fixture.ObservableForProperty(x => x.IsOnlyOneWord).Select(x => x.GetValue())).Subscribe(output.Add); foreach (var v in input) { @@ -247,7 +247,7 @@ public async Task Value_ConvertsChangesToValues() var fixture = new TestFixture(); - ObservableMixins.WhereNotNull(fixture.ObservableForProperty(x => x.IsOnlyOneWord).Value()).Subscribe(output.Add); + _ = ObservableMixins.WhereNotNull(fixture.ObservableForProperty(x => x.IsOnlyOneWord).Value()).Subscribe(output.Add); foreach (var v in input) { diff --git a/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphNameOfTestFixture.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphNameOfTestFixture.cs index cc6ca5e6b4..9192c0d139 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphNameOfTestFixture.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/Mocks/OaphNameOfTestFixture.cs @@ -31,7 +31,7 @@ public class OaphNameOfTestFixture : TestFixture Justification = "OAPH initialization requires 'this' in the constructor; single-threaded test fixture.")] public OaphNameOfTestFixture() { - this.WhenAnyValue(static x => x.IsOnlyOneWord) + _ = this.WhenAnyValue(static x => x.IsOnlyOneWord) .Select(static x => x ?? string.Empty) .Select(static x => x.Length >= LetterCount ? x.Substring(0, LetterCount) : x) .ToProperty( diff --git a/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveObjectTests.cs b/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveObjectTests.cs index 03984d7b97..b11218ba76 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveObjectTests.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveObjects/ReactiveObjectTests.cs @@ -45,7 +45,7 @@ public async Task ChangingShouldAlwaysArriveBeforeChanged() var beforeFired = false; string? changingPropertyName = null; string? changingValue = null; - fixture.Changing.Subscribe(x => + _ = fixture.Changing.Subscribe(x => { changingPropertyName = x.PropertyName; changingValue = fixture.IsOnlyOneWord; @@ -55,7 +55,7 @@ public async Task ChangingShouldAlwaysArriveBeforeChanged() var afterFired = false; string? changedPropertyName = null; string? changedValue = null; - fixture.Changed.Subscribe(x => + _ = fixture.Changed.Subscribe(x => { changedPropertyName = x.PropertyName; changedValue = fixture.IsOnlyOneWord; @@ -144,7 +144,7 @@ public async Task ExceptionsThrownInSubscribersShouldMarshalToThrownExceptions() { var fixture = new TestFixture { IsOnlyOneWord = FooText }; - fixture.Changed.Subscribe(static _ => throw new InvalidOperationException("Die!")); + _ = fixture.Changed.Subscribe(static _ => throw new InvalidOperationException("Die!")); var exceptionList = fixture.ThrownExceptions.Collect(); fixture.IsOnlyOneWord = BarText; @@ -159,7 +159,7 @@ public async Task ObservableForPropertyUsingExpression() const int ExpectedCount = 2; var fixture = new TestFixture { IsNotNullString = FooText, IsOnlyOneWord = BazText }; var output = new List>(); - ObservableMixins.WhereNotNull(fixture.ObservableForProperty(x => x.IsNotNullString)).Subscribe(output.Add); + _ = ObservableMixins.WhereNotNull(fixture.ObservableForProperty(x => x.IsNotNullString)).Subscribe(output.Add); fixture.IsNotNullString = BarText; fixture.IsNotNullString = BazText; @@ -188,7 +188,7 @@ public async Task RaiseAndSetUsingExpression() { var fixture = new TestFixture { IsNotNullString = FooText, IsOnlyOneWord = BazText }; var output = new List(); - fixture.Changed.Where(x => x.PropertyName is not null).Select(x => x.PropertyName!).Subscribe(output.Add); + _ = fixture.Changed.Where(x => x.PropertyName is not null).Select(x => x.PropertyName!).Subscribe(output.Add); fixture.UsesExprRaiseSet = FooText; fixture.UsesExprRaiseSet = FooText; // This one shouldn't raise a change notification @@ -257,9 +257,9 @@ public void ReactiveObjectShouldRethrowException() { var fixture = new TestFixture(); var observable = fixture.WhenAnyValue(x => x.IsOnlyOneWord).Skip(1); - observable.Subscribe(_ => throw new InvalidOperationException("This is a test.")); + _ = observable.Subscribe(_ => throw new InvalidOperationException("This is a test.")); - Assert.Throws(() => fixture.IsOnlyOneWord = "Two Words"); + _ = Assert.Throws(() => fixture.IsOnlyOneWord = "Two Words"); } /// Performs a ReactiveObject smoke test. @@ -271,8 +271,8 @@ public async Task ReactiveObjectSmokeTest() var output = new List(); var fixture = new TestFixture(); - fixture.Changing.Where(x => x.PropertyName is not null).Select(x => x.PropertyName!).Subscribe(outputChanging.Add); - fixture.Changed.Where(x => x.PropertyName is not null).Select(x => x.PropertyName!).Subscribe(output.Add); + _ = fixture.Changing.Where(x => x.PropertyName is not null).Select(x => x.PropertyName!).Subscribe(outputChanging.Add); + _ = fixture.Changed.Where(x => x.PropertyName is not null).Select(x => x.PropertyName!).Subscribe(output.Add); fixture.IsNotNullString = "Foo Bar Baz"; fixture.IsOnlyOneWord = FooText; diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyBasicTests.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyBasicTests.cs index ddd51fc6b1..b95165833c 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyBasicTests.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyBasicTests.cs @@ -88,7 +88,7 @@ public async Task AllowDuplicateValuesSendsMultipleIdenticalValues() { using var rp = ReactiveProperty.Create(0, Sequencer.Immediate, false, true); var values = new List(); - rp.Subscribe(values.Add); + _ = rp.Subscribe(values.Add); var initialCount = values.Count; @@ -143,7 +143,7 @@ public async Task DistinctUntilChangedDoesNotSendDuplicates() { using var rp = ReactiveProperty.Create(0, Sequencer.Immediate, false, false); var values = new List(); - rp.Subscribe(values.Add); + _ = rp.Subscribe(values.Add); var initialCount = values.Count; @@ -204,11 +204,11 @@ public async Task MultipleSubscribersReceiveUpdates() var values1 = new List(); var values2 = new List(); - rp.Subscribe(values1.Add); + _ = rp.Subscribe(values1.Add); rp.Value = 1; - rp.Subscribe(values2.Add); + _ = rp.Subscribe(values2.Add); rp.Value = SecondValue; @@ -243,7 +243,7 @@ public async Task ObserveErrorChangedEmitsErrors() { using var rp = ReactiveProperty.Create(null, Sequencer.Immediate, false, false); var errors = new List(); - rp.ObserveErrorChanged.Subscribe(errors.Add); + _ = rp.ObserveErrorChanged.Subscribe(errors.Add); _ = rp.AddValidationError(x => string.IsNullOrEmpty(x) ? RequiredError : null); @@ -257,7 +257,7 @@ public async Task ObserveHasErrorsEmitsErrorState() { using var rp = ReactiveProperty.Create(null, Sequencer.Immediate, false, false); var hasErrorsValues = new List(); - rp.ObserveHasErrors.Subscribe(hasErrorsValues.Add); + _ = rp.ObserveHasErrors.Subscribe(hasErrorsValues.Add); _ = rp.AddValidationError(x => string.IsNullOrEmpty(x) ? RequiredError : null); @@ -287,7 +287,7 @@ public async Task PropertyChangedEventFires() using (Assert.Multiple()) { await Assert.That(fired).IsTrue(); - await Assert.That(propertyName).IsEqualTo(nameof(ReactiveProperty.Value)); + await Assert.That(propertyName).IsEqualTo(nameof(ReactiveProperty<>.Value)); } } @@ -298,7 +298,7 @@ public async Task RefreshSendsCurrentValueEvenIfUnchanged() { using var rp = ReactiveProperty.Create(InitialValue, Sequencer.Immediate, false, false); var values = new List(); - rp.Subscribe(values.Add); + _ = rp.Subscribe(values.Add); var countBefore = values.Count; @@ -316,7 +316,7 @@ public async Task SchedulerIsUsedForNotifications() var scheduler = TestContext.Current.GetVirtualTimeScheduler(); using var rp = ReactiveProperty.Create(0, scheduler, false, false); var values = new List(); - rp.Subscribe(values.Add); + _ = rp.Subscribe(values.Add); // Value should not be received until scheduler advances await Assert.That(values).IsEmpty(); @@ -332,7 +332,7 @@ public async Task SkipCurrentValueOnSubscribe() { using var rp = ReactiveProperty.Create(InitialValue, Sequencer.Immediate, true, false); var values = new List(); - rp.Subscribe(values.Add); + _ = rp.Subscribe(values.Add); await Assert.That(values).IsEmpty(); // Should not receive initial value @@ -367,7 +367,7 @@ public async Task SubscribeAfterDisposeCompletesImmediately() rp.Dispose(); var completed = false; - rp.Subscribe( + _ = rp.Subscribe( _ => { }, () => completed = true); @@ -381,7 +381,7 @@ public async Task SubscribeReceivesCurrentValue() { using var rp = ReactiveProperty.Create(InitialValue, Sequencer.Immediate, false, false); var received = 0; - rp.Subscribe(x => received = x); + _ = rp.Subscribe(x => received = x); await Assert.That(received).IsEqualTo(InitialValue); } @@ -394,7 +394,7 @@ public async Task SubscribeReceivesValueChanges() const int SecondValue = 2; using var rp = ReactiveProperty.Create(0, Sequencer.Immediate, false, false); var values = new List(); - rp.Subscribe(values.Add); + _ = rp.Subscribe(values.Add); rp.Value = 1; rp.Value = SecondValue; @@ -433,4 +433,64 @@ public async Task ValuePropertySetterUpdatesValue() rp.Value = "new value"; await Assert.That(rp.Value).IsEqualTo("new value"); } + + /// Verifies the parameterless constructor creates a property with the default value. + /// A task representing the asynchronous test. + [Test] + public async Task ParameterlessConstructorCreatesDefaultValue() + { + using var rp = new ReactiveProperty(); + await Assert.That(rp.Value).IsEqualTo(0); + } + + /// Verifies the single-value constructor stores the supplied value. + /// A task representing the asynchronous test. + [Test] + public async Task SingleValueConstructorStoresValue() + { + using var rp = new ReactiveProperty(InitialValue); + await Assert.That(rp.Value).IsEqualTo(InitialValue); + } + + /// Verifies the value/skip/duplicate constructor stores the supplied value and applies skip-current. + /// A task representing the asynchronous test. + [Test] + public async Task ValueSkipDuplicateConstructorStoresValueAndSkips() + { + using var rp = new ReactiveProperty(InitialValue, true, false); + var values = new List(); + _ = rp.Subscribe(values.Add); + + using (Assert.Multiple()) + { + await Assert.That(rp.Value).IsEqualTo(InitialValue); + await Assert.That(values).IsEmpty(); // skip-current means no initial emission + } + } + + /// Verifies that disposing a property twice is a no-op and remains disposed. + /// A task representing the asynchronous test. + [Test] + public async Task DisposeTwiceIsNoOp() + { + var rp = ReactiveProperty.Create(InitialValue); + + rp.Dispose(); + rp.Dispose(); // second call hits the already-disposed early-return guard + + await Assert.That(rp.IsDisposed).IsTrue(); + } + + /// Verifies that setting a new value after disposal does not throw and keeps the assigned value. + /// A task representing the asynchronous test. + [Test] + public async Task SetValueAfterDisposeStoresValueWithoutNotifying() + { + var rp = ReactiveProperty.Create(InitialValue); + rp.Dispose(); + + rp.Value = UpdatedValue; // SetValue takes the IsDisposed early-return path + + await Assert.That(rp.Value).IsEqualTo(UpdatedValue); + } } diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyTest.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyTest.cs index aca798ef36..a8b4d08047 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyTest.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyTest.cs @@ -60,7 +60,7 @@ public async Task DefaultValueIsRaisedOnSubscribe() using var rp = new ReactiveProperty(null, Sequencer.Immediate, false, false); await Assert.That(rp.Value).IsNull(); var receivedValue = false; - rp.Subscribe(x => receivedValue = true); + _ = rp.Subscribe(x => receivedValue = true); await Assert.That(receivedValue).IsTrue(); } @@ -117,7 +117,7 @@ public async Task InitialValue() using var rp = new ReactiveProperty(ReactiveUiValue, Sequencer.Immediate, false, false); await Assert.That(rp.Value).IsEqualTo(ReactiveUiValue); string? received = null; - rp.Subscribe(x => received = x); + _ = rp.Subscribe(x => received = x); await Assert.That(received).IsEqualTo(ReactiveUiValue); } @@ -131,7 +131,7 @@ public async Task InitialValueSkipCurrent() // current value should be skipped string? received = null; - rp.Subscribe(x => received = x); + _ = rp.Subscribe(x => received = x); rp.Value = ReactiveUiSecondValue; await Assert.That(received).IsEqualTo(ReactiveUiSecondValue); await Assert.That(rp.Value).IsEqualTo(ReactiveUiSecondValue); @@ -148,7 +148,7 @@ public async Task MultipleSubscribersGetCurrentValue() var collector1 = new List(); var collector2 = new List(); var obs = rp; - obs.Subscribe(collector1.Add); + _ = obs.Subscribe(collector1.Add); await Assert.That(rp.Value).IsEqualTo(0); await Assert.That(collector1).IsEquivalentTo([0]); @@ -162,7 +162,7 @@ public async Task MultipleSubscribersGetCurrentValue() await Assert.That(collector1).IsEquivalentTo([0, 1, ThirdValue]); // second subscriber - obs.Subscribe(collector2.Add); + _ = obs.Subscribe(collector2.Add); await Assert.That(rp.Value).IsEqualTo(ThirdValue); await Assert.That(collector2).IsEquivalentTo([ThirdValue]); @@ -182,7 +182,7 @@ public async Task ObserveErrors() const int ExpectedEmissionCount = 2; var results = new List(); - rp.ObserveErrorChanged.Subscribe(results.Add); + _ = rp.ObserveErrorChanged.Subscribe(results.Add); rp.Value = "OK"; await Assert.That(results.Count).IsEqualTo(ExpectedEmissionCount); @@ -200,7 +200,7 @@ public async Task ObserveHasError() const int ExpectedEmissionCount = 2; var results = new List(); - rp.ObserveHasErrors.Subscribe(results.Add); + _ = rp.ObserveHasErrors.Subscribe(results.Add); rp.Value = "OK"; await Assert.That(results.Count).IsEqualTo(ExpectedEmissionCount); @@ -215,7 +215,7 @@ public async Task ObserveValidationErrors_HandlesMultipleErrors() { var target = new ReactivePropertyVm(); var errors = new List(); - target.LengthLessThanFiveProperty.ObserveValidationErrors().Subscribe(errors.Add); + _ = target.LengthLessThanFiveProperty.ObserveValidationErrors().Subscribe(errors.Add); await Assert.That(errors).Count().IsEqualTo(1); await Assert.That(errors[0]).IsEqualTo(RequiredErrorValue); @@ -234,7 +234,7 @@ public async Task ObserveValidationErrors_ReturnsErrorMessages() { var target = new ReactivePropertyVm(); var errors = new List(); - target.IsRequiredProperty.ObserveValidationErrors().Subscribe(errors.Add); + _ = target.IsRequiredProperty.ObserveValidationErrors().Subscribe(errors.Add); const int AfterValidCount = 2; const int AfterClearedCount = 3; @@ -267,7 +267,7 @@ public async Task Refresh() { using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false); var collector = new List(); - rp.Subscribe(collector.Add); + _ = rp.Subscribe(collector.Add); await Assert.That(collector).IsEquivalentTo([0]); @@ -286,7 +286,7 @@ public async Task SetValueRaisesEvents() rp.Value = ReactiveUiValue; await Assert.That(rp.Value).IsEqualTo(ReactiveUiValue); string? received = null; - rp.Subscribe(x => received = x); + _ = rp.Subscribe(x => received = x); await Assert.That(received).IsEqualTo(ReactiveUiValue); } @@ -324,7 +324,7 @@ public async Task ValidationErrorChangedTest() .AddValidationError(x => string.IsNullOrWhiteSpace(x) ? ErrorLowerValue : null); // old version behavior - rprop.ObserveErrorChanged.Skip(1).Subscribe(errors.Add); + _ = rprop.ObserveErrorChanged.Skip(1).Subscribe(errors.Add); await Assert.That(errors.Count).IsEqualTo(0); @@ -358,7 +358,7 @@ public async Task ValidationIsRequiredIsCorrectlyHandled() { var target = new ReactivePropertyVm(); var errors = new List(); - target.IsRequiredProperty + _ = target.IsRequiredProperty .ObserveErrorChanged.Where(x => x is not null).Subscribe(errors.Add); await Assert.That(errors.Count).IsEqualTo(1); @@ -383,7 +383,7 @@ public async Task ValidationLengthIsCorrectlyHandled() { var target = new ReactivePropertyVm(); IEnumerable? error = null; - target.LengthLessThanFiveProperty + _ = target.LengthLessThanFiveProperty .ObserveErrorChanged.Subscribe(x => error = x); await Assert.That(target.LengthLessThanFiveProperty.HasErrors).IsTrue(); @@ -410,7 +410,7 @@ public async Task ValidationTaskTest() { var target = new ReactivePropertyVm(); var errors = new List(); - target.TaskValidationTestProperty + _ = target.TaskValidationTestProperty .ObserveErrorChanged.Where(x => x is not null).Subscribe(errors.Add); await Assert.That(errors.Count).IsEqualTo(1); await Assert.That(errors[0]!.OfType()).IsEquivalentTo([RequiredErrorValue]); @@ -435,7 +435,7 @@ public async Task ValidationWithAsyncFailedCase() .AddValidationError(x => string.IsNullOrEmpty(x) ? null : ErrorMessage); IEnumerable? error = null; - rp.ObserveErrorChanged.Subscribe(x => error = x); + _ = rp.ObserveErrorChanged.Subscribe(x => error = x); await Assert.That(rp.HasErrors).IsFalse(); await Assert.That(error is null).IsTrue(); @@ -458,7 +458,7 @@ public async Task ValidationWithAsyncSuccessCase() .AddValidationError(_ => tcs.Task); IEnumerable? error = null; - rp.ObserveErrorChanged.Subscribe(x => error = x); + _ = rp.ObserveErrorChanged.Subscribe(x => error = x); await Assert.That(rp.HasErrors).IsFalse(); await Assert.That(error is null).IsTrue(); @@ -494,7 +494,7 @@ public async Task ValidationWithAsyncThrottleTest() #endif IEnumerable? error = null; - rp.ObserveErrorChanged.Subscribe(x => error = x); + _ = rp.ObserveErrorChanged.Subscribe(x => error = x); scheduler.AdvanceTo(DateTimeOffset.MinValue.Add(TimeSpan.FromMilliseconds(0))); rp.Value = string.Empty; @@ -576,7 +576,7 @@ public async Task ValueUpdatesMultipleTimesWithDifferentValues() const int FourthValue = 3; using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false); var collector = new List(); - rp.Subscribe(collector.Add); + _ = rp.Subscribe(collector.Add); await Assert.That(rp.Value).IsEqualTo(0); await Assert.That(collector).IsEquivalentTo([0]); @@ -601,7 +601,7 @@ public async Task ValueUpdatesMultipleTimesWithSameValues() { using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, true); var collector = new List(); - rp.Subscribe(collector.Add); + _ = rp.Subscribe(collector.Add); await Assert.That(rp.Value).IsEqualTo(0); await Assert.That(collector).IsEquivalentTo([0]); diff --git a/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyValidationTests.cs b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyValidationTests.cs index a26068dcf6..be73876be9 100644 --- a/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyValidationTests.cs +++ b/src/tests/ReactiveUI.Tests/ReactiveProperties/ReactivePropertyValidationTests.cs @@ -3,6 +3,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Collections; using System.Linq; namespace ReactiveUI.Tests.ReactiveProperties; @@ -22,6 +23,12 @@ public class ReactivePropertyValidationTests /// Error message produced by the second of two chained validators. private const string PositiveError = "positive"; + /// The number of polling attempts awaited for an asynchronous validator to complete. + private const int PollAttempts = 500; + + /// The delay between polling attempts, in milliseconds. + private const int PollDelayMilliseconds = 10; + /// Verifies the observable-based validator overload surfaces and clears errors as the value changes. /// A representing the asynchronous operation. [Test] @@ -66,4 +73,199 @@ public async Task MultipleValidators_AggregateErrors() rp.Value = 1; await Assert.That(rp.GetErrors(null)?.OfType()).Contains(PositiveError); } + + /// Verifies the single-argument observable-enumerable validator overload defaults to validating the initial value. + /// A representing the asynchronous operation. + [Test] + public async Task ObservableEnumerableValidator_SingleArgOverload() + { + using var rp = new ReactiveProperty(null, Sequencer.Immediate, false, false) + .AddValidationError(xs => + new MapSignal(xs, static x => string.IsNullOrEmpty(x) ? new[] { ObservableError } : null)); + + await Assert.That(rp.HasErrors).IsTrue(); + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(ObservableError); + } + + /// Verifies the single-argument asynchronous-enumerable validator overload defaults to validating the initial value. + /// A representing the asynchronous operation. + [Test] + public async Task AsyncEnumerableValidator_SingleArgOverload() + { + using var rp = new ReactiveProperty(-1, Sequencer.Immediate, false, false) + .AddValidationError(static x => + Task.FromResult(x < 0 ? new[] { AsyncError } : null)); + + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(AsyncError); + } + + /// Verifies the single-argument asynchronous-string validator overload defaults to validating the initial value. + /// A representing the asynchronous operation. + [Test] + public async Task AsyncStringValidator_SingleArgOverload() + { + using var rp = new ReactiveProperty(-1, Sequencer.Immediate, false, false) + .AddValidationError(static x => Task.FromResult(x < 0 ? AsyncError : null)); + + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(AsyncError); + } + + /// Verifies that two manually driven validators aggregate only once both have reported, mixing string and non-string error elements. + /// A representing the asynchronous operation. + [Test] + public async Task ManualValidators_AggregateMixedErrorElements() + { + var sig0 = new Signal(); + var sig1 = new Signal(); + + using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false) + .AddValidationError(_ => sig0) + .AddValidationError(_ => sig1); + + // Only the first validator has reported: aggregation must wait for the second. + sig0.OnNext(new object[] { NegativeError }); + await Assert.That(rp.HasErrors).IsFalse(); + + // Both validators have now reported; a non-string element and a string element are aggregated. + sig1.OnNext(new[] { PositiveError }); + + using (Assert.Multiple()) + { + await Assert.That(rp.HasErrors).IsTrue(); + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(NegativeError); + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(PositiveError); + } + + // A later all-null report clears the aggregate. + sig0.OnNext(null); + sig1.OnNext(null); + await Assert.That(rp.HasErrors).IsFalse(); + } + + /// Verifies that an error from any validator stream is forwarded and stops further aggregation. + /// A representing the asynchronous operation. + [Test] + public async Task ManualValidator_ErrorIsForwardedAndStops() + { + var erroring = new Signal(); + var secondErroring = new Signal(); + var live = new Signal(); + + using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false) + .AddValidationError(_ => erroring) + .AddValidationError(_ => secondErroring) + .AddValidationError(_ => live); + + // The first validator errors, terminating the aggregate sink. + erroring.OnError(new InvalidOperationException("boom")); + + // A second error after the sink has stopped takes the already-stopped early-return path. + secondErroring.OnError(new InvalidOperationException("second")); + + // An emission from a still-live validator after the stop is ignored (stopped guard in OnNextAt). + live.OnNext(new[] { NegativeError }); + + await Assert.That(rp.HasErrors).IsFalse(); + } + + /// Verifies that disposing a property with an asynchronous validator completes the validator's source cleanly. + /// A representing the asynchronous operation. + [Test] + public async Task AsyncValidator_DisposeCompletesSource() + { + var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false) + .AddValidationError(static x => Task.FromResult(x < 0 ? AsyncError : null)); + + rp.Value = -1; + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(AsyncError); + + // Disposing completes the validator's source observable, exercising the async sink's completion accounting + // and the aggregate sink's subscription disposal. + rp.Dispose(); + + await Assert.That(rp.IsDisposed).IsTrue(); + } + + /// Verifies that the aggregate downstream completes only after every validator stream has completed. + /// A representing the asynchronous operation. + [Test] + public async Task ManualValidators_CompleteWhenAllComplete() + { + var sig0 = new Signal(); + var sig1 = new Signal(); + + using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false) + .AddValidationError(_ => sig0) + .AddValidationError(_ => sig1); + + // Both validators report so the aggregate has a full set of latest values. + sig0.OnNext(new[] { NegativeError }); + sig1.OnNext(null); + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(NegativeError); + + // First completion is not enough to terminate the aggregate downstream. + sig0.OnCompleted(); + + // The still-open second validator can continue to drive aggregation. + sig1.OnNext(new[] { PositiveError }); + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(PositiveError); + + // Completing the last validator terminates the aggregate downstream. + sig1.OnCompleted(); + await Assert.That(rp.HasErrors).IsTrue(); + } + + /// Verifies the asynchronous validator forwards a faulted task as a no-op error and the property remains usable. + /// A representing the asynchronous operation. + [Test] + public async Task AsyncValidator_FaultedTaskIsHandled() + { + using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false) + .AddValidationError(static _ => Task.FromException(new InvalidOperationException("nope"))); + + rp.Value = -1; + + // The faulted task surfaces as an error on the validator stream (forwarded as a no-op downstream), + // so no validation errors are recorded and the property stays alive. + await Assert.That(rp.HasErrors).IsFalse(); + } + + /// Verifies the asynchronous validator forwards an exception thrown synchronously by the selector. + /// A representing the asynchronous operation. + [Test] + public async Task AsyncValidator_SelectorThrowsSynchronously() + { + using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false) + .AddValidationError(static x => x < 0 + ? throw new InvalidOperationException("sync throw") + : Task.FromResult(null)); + + rp.Value = -1; + + await Assert.That(rp.HasErrors).IsFalse(); + } + + /// Verifies the asynchronous validator delivers a result from a genuinely awaited task. + /// A representing the asynchronous operation. + [Test] + public async Task AsyncValidator_AwaitedTaskDeliversResult() + { + using var rp = new ReactiveProperty(0, Sequencer.Immediate, false, false) + .AddValidationError( + async x => + { + await Task.Yield(); + return x < 0 ? AsyncError : null; + }, + true); + + rp.Value = -1; + + for (var attempt = 0; attempt < PollAttempts && !rp.HasErrors; attempt++) + { + await Task.Delay(PollDelayMilliseconds); + } + + await Assert.That(rp.GetErrors(null)?.OfType()).Contains(AsyncError); + } } diff --git a/src/tests/ReactiveUI.Tests/Resolvers/InpcObservableForPropertyTests.cs b/src/tests/ReactiveUI.Tests/Resolvers/InpcObservableForPropertyTests.cs index 8db8fca7f6..024b8bff7f 100644 --- a/src/tests/ReactiveUI.Tests/Resolvers/InpcObservableForPropertyTests.cs +++ b/src/tests/ReactiveUI.Tests/Resolvers/InpcObservableForPropertyTests.cs @@ -51,7 +51,7 @@ public async Task NotificationOnPropertyChanged() var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException(PropertyNameNullMessage); - ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName)).Subscribe(changes.Add); + _ = ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName)).Subscribe(changes.Add); const int ExpectedChangeCount = 2; testClass.Property1 = "test1"; @@ -82,7 +82,7 @@ public async Task NotificationOnPropertyChanging() var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException(PropertyNameNullMessage); - ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName, true)).Subscribe(changes.Add); + _ = ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName, true)).Subscribe(changes.Add); const int ExpectedChangeCount = 2; testClass.Property1 = "test1"; @@ -113,7 +113,7 @@ public async Task NotificationOnWholeObjectChanged() var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException(PropertyNameNullMessage); - ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName)).Subscribe(changes.Add); + _ = ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName)).Subscribe(changes.Add); const int ExpectedChangeCount = 2; @@ -147,7 +147,7 @@ public async Task NotificationOnWholeObjectChanging() var propertyName = exp.GetMemberInfo()?.Name ?? throw new InvalidOperationException(PropertyNameNullMessage); - ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName, true)).Subscribe(changes.Add); + _ = ObservableMixins.WhereNotNull(instance.GetNotificationForProperty(testClass, exp, propertyName, true)).Subscribe(changes.Add); const int ExpectedChangeCount = 2; @@ -165,6 +165,68 @@ public async Task NotificationOnWholeObjectChanging() } } + /// The two-argument affinity overload defers to changed notifications, and a null type has no affinity. + /// A task representing the asynchronous test. + [Test] + public async Task GetAffinityForObjectTwoArgOverloadAndNullType() + { + var instance = new INPCObservableForProperty(); + + using (Assert.Multiple()) + { + await Assert.That(instance.GetAffinityForObject(typeof(TestClassChanged), "Property1")).IsEqualTo(BindingAffinity.Explicit); + await Assert.That(instance.GetAffinityForObject(null, "Property1", false)).IsEqualTo(0); + } + } + + /// A changing notification ignores other properties' events and unsubscribes on dispose. + /// A task representing the asynchronous test. + [Test] + public async Task ChangingNotificationIgnoresOtherPropertiesAndDisposes() + { + var instance = new INPCObservableForProperty(); + var testClass = new TestClassChanging(); + + Expression> expr = x => x.Property1; + var exp = Reflection.Rewrite(expr.Body); + var propertyName = exp.GetMemberInfo()?.Name ?? + throw new InvalidOperationException(PropertyNameNullMessage); + + var changes = new List>(); + var subscription = instance.GetNotificationForProperty(testClass, exp, propertyName, true).Subscribe(changes.Add); + + // A non-matching property's changing event is filtered out. + testClass.Property2 = "ignored"; + await Assert.That(changes).IsEmpty(); + + // The observed property's changing event is forwarded. + testClass.Property1 = "matched"; + await Assert.That(changes).IsNotEmpty(); + + subscription.Dispose(); + } + + /// A non-notifying sender observed through an index expression yields a silent stream, exercising both the + /// index-named-property path and the "sender is not a notifier" fallback. + /// A task representing the asynchronous test. + [Test] + public async Task NonNotifyingSenderWithIndexExpressionReturnsSilentStream() + { + var instance = new INPCObservableForProperty(); + + Expression, string>> expr = x => x[0]; + var indexExpression = Reflection.Rewrite(expr.Body); + await Assert.That(indexExpression.NodeType).IsEqualTo(ExpressionType.Index); + + var changes = new List>(); + + // A plain object is neither INotifyPropertyChanged nor INotifyPropertyChanging, so the index-named + // observation resolves to a silent (no-op) stream rather than hooking change events. + using var subscription = instance.GetNotificationForProperty(new object(), indexExpression, "Item").Subscribe(changes.Add); + + await Assert.That(changes).IsEmpty(); + } + /// A test fixture implementing to drive change notifications. private sealed class TestClassChanged : INotifyPropertyChanged { diff --git a/src/tests/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs b/src/tests/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs index 79e31836ec..bc2e8920ba 100644 --- a/src/tests/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs +++ b/src/tests/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs @@ -139,7 +139,7 @@ public void GetNotificationForPropertyThrowsOnNullSender() var instance = new POCOObservableForProperty(); Expression> expr = x => x.Property1; - Assert.Throws(() => + _ = Assert.Throws(() => instance.GetNotificationForProperty(null!, expr.Body, nameof(PocoType.Property1), false, true)); } diff --git a/src/tests/ReactiveUI.Tests/Routing/RoutingStateMixinsTests.cs b/src/tests/ReactiveUI.Tests/Routing/RoutingStateMixinsTests.cs new file mode 100644 index 0000000000..4357372c41 --- /dev/null +++ b/src/tests/ReactiveUI.Tests/Routing/RoutingStateMixinsTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveUI.Tests.Routing; + +/// Tests for . +public class RoutingStateMixinsTests +{ + /// Searching skips non-matching entries (the type test's false branch) before returning a match (its true branch). + /// A representing the asynchronous operation. + [Test] + public async Task FindViewModelInStackReturnsMatchAfterSkippingNonMatches() + { + var state = new RoutingState(); + var first = new FirstViewModel(); + state.NavigationStack.Add(first); + state.NavigationStack.Add(new SecondViewModel()); + + // Reverse search visits the SecondViewModel (no match) before the FirstViewModel (match), + // exercising both branches of the `stack[i] is T` test. + await Assert.That(state.FindViewModelInStack()).IsEqualTo(first); + } + + /// Searching a stack with no matching type returns the default. + /// A representing the asynchronous operation. + [Test] + public async Task FindViewModelInStackReturnsNullWhenNoMatch() + { + var state = new RoutingState(); + state.NavigationStack.Add(new SecondViewModel()); + + await Assert.That(state.FindViewModelInStack()).IsNull(); + } + + /// The current view model is the top of the stack, or null when the stack is empty. + /// A representing the asynchronous operation. + [Test] + public async Task GetCurrentViewModelReturnsTopOrNull() + { + var state = new RoutingState(); + + await Assert.That(state.GetCurrentViewModel()).IsNull(); + + var first = new FirstViewModel(); + var second = new SecondViewModel(); + state.NavigationStack.Add(first); + state.NavigationStack.Add(second); + + await Assert.That(state.GetCurrentViewModel()).IsEqualTo(second); + } + + /// A routable view model used to populate the navigation stack. + private sealed class FirstViewModel : ReactiveObject, IRoutableViewModel + { + /// + public string? UrlPathSegment => "first"; + + /// + public IScreen HostScreen => null!; + } + + /// A second routable view model type used to populate the navigation stack. + private sealed class SecondViewModel : ReactiveObject, IRoutableViewModel + { + /// + public string? UrlPathSegment => "second"; + + /// + public IScreen HostScreen => null!; + } +} diff --git a/src/tests/ReactiveUI.Tests/RxSchedulersTest.cs b/src/tests/ReactiveUI.Tests/RxSchedulersTest.cs index 1d3e5a7554..339ef67cc7 100644 --- a/src/tests/ReactiveUI.Tests/RxSchedulersTest.cs +++ b/src/tests/ReactiveUI.Tests/RxSchedulersTest.cs @@ -91,4 +91,19 @@ public async Task SchedulersProvideBasicFunctionality() } } } + + /// Forcing the static constructor to run leaves both default schedulers assigned. + /// A representing the asynchronous operation. + [Test] + [TestExecutor] + public async Task EnsureStaticConstructorRunAssignsDefaults() + { + RxSchedulers.EnsureStaticConstructorRun(); + + using (Assert.Multiple()) + { + await Assert.That(RxSchedulers.MainThreadScheduler).IsNotNull(); + await Assert.That(RxSchedulers.TaskpoolScheduler).IsNotNull(); + } + } } diff --git a/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs b/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs index d4b82a4298..21c614868a 100644 --- a/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs +++ b/src/tests/ReactiveUI.Tests/ScheduledSubjectTest.cs @@ -53,7 +53,7 @@ public async Task OnCompleted_CompletesObservable() var subject = new ScheduledSubject(scheduler); var completed = false; - subject.Subscribe(_ => { }, () => completed = true); + _ = subject.Subscribe(_ => { }, () => completed = true); subject.OnCompleted(); scheduler.Start(); @@ -70,7 +70,7 @@ public async Task OnError_SendsErrorToObservers() var subject = new ScheduledSubject(scheduler); Exception? receivedError = null; - subject.Subscribe(_ => { }, ex => receivedError = ex); + _ = subject.Subscribe(_ => { }, ex => receivedError = ex); var error = new InvalidOperationException("Test error"); subject.OnError(error); scheduler.Start(); @@ -90,7 +90,7 @@ public async Task OnNext_EmitsValues() const int SecondValue = 2; const int ExpectedCount = 2; - subject.Subscribe(results.Add); + _ = subject.Subscribe(results.Add); subject.OnNext(1); subject.OnNext(SecondValue); scheduler.Start(); @@ -124,7 +124,7 @@ public async Task Subscribe_SchedulesOnSpecifiedScheduler() var subject = new ScheduledSubject(scheduler); var results = new List(); - subject.Subscribe(results.Add); + _ = subject.Subscribe(results.Add); subject.OnNext(1); await Assert.That(results).IsEmpty(); diff --git a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostExtensionsAotTests.cs b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostExtensionsAotTests.cs index fdda5cd3a5..472df6a5f5 100644 --- a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostExtensionsAotTests.cs +++ b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostExtensionsAotTests.cs @@ -694,14 +694,9 @@ public IObservable InvalidateState() public IObservable LoadState() { LoadStateCallCount++; - if (ShouldThrowOnLoad) - { - return Signal.Fail( + return ShouldThrowOnLoad ? Signal.Fail( new InvalidOperationException("Failed to load state"), - Sequencer.Immediate); - } - - return Signal.Emit((object?)StateToLoad, Sequencer.Immediate); + Sequencer.Immediate) : Signal.Emit((object?)StateToLoad, Sequencer.Immediate); } /// @@ -715,12 +710,7 @@ public IObservable InvalidateState() Sequencer.Immediate); } - if (StateToLoad is TState typedState) - { - return Signal.Emit(typedState, Sequencer.Immediate); - } - - return Signal.Emit(default, Sequencer.Immediate); + return StateToLoad is TState typedState ? Signal.Emit(typedState, Sequencer.Immediate) : Signal.Emit(default, Sequencer.Immediate); } /// diff --git a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostGenericTests.cs b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostGenericTests.cs index de6b7a9dcf..450d4c7edd 100644 --- a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostGenericTests.cs +++ b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostGenericTests.cs @@ -33,12 +33,12 @@ public async Task Constructor_DefaultObservables_ThrowExceptionOnSubscribe() var gotErrorInvalidate = false; var gotErrorPersist = false; - host.IsLaunchingNew.Subscribe(_ => { }, ex => gotErrorLaunching = true); - host.IsResuming.Subscribe(_ => { }, ex => gotErrorResuming = true); - host.IsUnpausing.Subscribe(_ => { }, ex => gotErrorUnpausing = true); - host.IsContinuing.Subscribe(_ => { }, ex => gotErrorContinuing = true); - host.ShouldInvalidateState.Subscribe(_ => { }, ex => gotErrorInvalidate = true); - host.ShouldPersistState.Subscribe(_ => { }, ex => gotErrorPersist = true); + _ = host.IsLaunchingNew.Subscribe(_ => { }, ex => gotErrorLaunching = true); + _ = host.IsResuming.Subscribe(_ => { }, ex => gotErrorResuming = true); + _ = host.IsUnpausing.Subscribe(_ => { }, ex => gotErrorUnpausing = true); + _ = host.IsContinuing.Subscribe(_ => { }, ex => gotErrorContinuing = true); + _ = host.ShouldInvalidateState.Subscribe(_ => { }, ex => gotErrorInvalidate = true); + _ = host.ShouldPersistState.Subscribe(_ => { }, ex => gotErrorPersist = true); await Assert.That(gotErrorLaunching).IsTrue(); await Assert.That(gotErrorResuming).IsTrue(); @@ -348,8 +348,7 @@ public async Task ISuspensionHost_AppState_SetWithValidValue_UpdatesTypedPropert [Test] public async Task ISuspensionHost_AppState_SetNull_SetsTypedPropertyToDefault() { - using var host = new SuspensionHost(); - host.AppStateValue = new(); + using var host = new SuspensionHost { AppStateValue = new() }; var untypedHost = (ISuspensionHost)host; untypedHost.AppState = null; @@ -390,9 +389,7 @@ public async Task ISuspensionHost_CreateNewAppState_GetProjectsFromTypedFactory( [Test] public async Task ISuspensionHost_CreateNewAppState_GetWhenTypedIsNull_ReturnsNull() { - using var host = new SuspensionHost(); - host.CreateNewAppStateTyped = null; - + using var host = new SuspensionHost { CreateNewAppStateTyped = null }; var untypedHost = (ISuspensionHost)host; var factory = untypedHost.CreateNewAppState; @@ -420,8 +417,7 @@ public async Task ISuspensionHost_CreateNewAppState_SetWithValidFactory_UpdatesT [Test] public async Task ISuspensionHost_CreateNewAppState_SetNull_SetsTypedPropertyToNull() { - using var host = new SuspensionHost(); - host.CreateNewAppStateTyped = () => new(); + using var host = new SuspensionHost { CreateNewAppStateTyped = () => new() }; var untypedHost = (ISuspensionHost)host; untypedHost.CreateNewAppState = null; diff --git a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs index a80925ec60..06c4197829 100644 --- a/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs +++ b/src/tests/ReactiveUI.Tests/Suspension/SuspensionHostTests.cs @@ -52,7 +52,7 @@ public async Task Constructor_DefaultObservables_ThrowExceptionOnSubscribe() using var host = new SuspensionHost(); var gotError = false; - host.IsLaunchingNew.Subscribe(_ => { }, ex => gotError = true); + _ = host.IsLaunchingNew.Subscribe(_ => { }, ex => gotError = true); await Assert.That(gotError).IsTrue(); } @@ -62,9 +62,7 @@ public async Task Constructor_DefaultObservables_ThrowExceptionOnSubscribe() [Test] public async Task CreateNewAppState_SetAndGet_ReturnsCorrectFunc() { - using var host = new SuspensionHost(); - host.CreateNewAppState = () => new DummyAppState(); - + using var host = new SuspensionHost { CreateNewAppState = () => new DummyAppState() }; await Assert.That(host.CreateNewAppState).IsNotNull(); await Assert.That(host.CreateNewAppState!()).IsTypeOf(); } diff --git a/src/tests/ReactiveUI.Tests/SuspensionHostExtensionsTests.cs b/src/tests/ReactiveUI.Tests/SuspensionHostExtensionsTests.cs index b7a5ca4874..c7bee173ce 100644 --- a/src/tests/ReactiveUI.Tests/SuspensionHostExtensionsTests.cs +++ b/src/tests/ReactiveUI.Tests/SuspensionHostExtensionsTests.cs @@ -503,14 +503,9 @@ public IObservable InvalidateState() public IObservable LoadState() { LoadStateCallCount++; - if (ShouldThrowOnLoad) - { - return Signal.Fail( + return ShouldThrowOnLoad ? Signal.Fail( new InvalidOperationException("Failed to load state"), - Sequencer.Immediate); - } - - return Signal.Emit(StateToLoad ?? new DummyAppState(), Sequencer.Immediate); + Sequencer.Immediate) : Signal.Emit(StateToLoad ?? new DummyAppState(), Sequencer.Immediate); } /// @@ -525,12 +520,7 @@ public IObservable InvalidateState() } // For test purposes, try to cast StateToLoad to T - if (StateToLoad is T typedState) - { - return Signal.Emit(typedState, Sequencer.Immediate); - } - - return Signal.Emit(default, Sequencer.Immediate); + return StateToLoad is T typedState ? Signal.Emit(typedState, Sequencer.Immediate) : Signal.Emit(default, Sequencer.Immediate); } /// diff --git a/src/tests/ReactiveUI.Tests/Utilities/DisposableMixinsTests.cs b/src/tests/ReactiveUI.Tests/Utilities/DisposableMixinsTests.cs index 1d1075d72c..4879a2ffae 100644 --- a/src/tests/ReactiveUI.Tests/Utilities/DisposableMixinsTests.cs +++ b/src/tests/ReactiveUI.Tests/Utilities/DisposableMixinsTests.cs @@ -20,8 +20,8 @@ public async Task DisposeWith_AddsToComposite() // Act const int ExpectedCount = 2; - disposable1.DisposeWith(compositeDisposable); - disposable2.DisposeWith(compositeDisposable); + _ = disposable1.DisposeWith(compositeDisposable); + _ = disposable2.DisposeWith(compositeDisposable); // Assert await Assert.That(compositeDisposable).Count().IsEqualTo(ExpectedCount); @@ -38,7 +38,7 @@ public async Task DisposeWith_DisposesWhenCompositeDisposed() var compositeDisposable = new MultipleDisposable(); // Act - disposable.DisposeWith(compositeDisposable); + _ = disposable.DisposeWith(compositeDisposable); compositeDisposable.Dispose(); // Assert diff --git a/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs b/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs index f758577f10..c218a35e31 100644 --- a/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs +++ b/src/tests/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs @@ -33,7 +33,7 @@ public async Task FactoryThrowsArgumentNullException_FallsBackToCurrentThread() ISequencer? schedulerExecutedOn = null; var schedulerFactory = new Func(() => throw new ArgumentNullException()); var sut = new WaitForDispatcherScheduler(schedulerFactory); - sut.Schedule( + _ = sut.Schedule( null!, (scheduler, _) => { @@ -57,7 +57,7 @@ public async Task FactoryThrowsException_ReCallsOnSchedule() }); var sut = new WaitForDispatcherScheduler(schedulerFactory); - sut.Schedule(() => { }); + _ = sut.Schedule(() => { }); const int ExpectedFactoryCalls = 2; await Assert.That(schedulerFactoryCalls).IsEqualTo(ExpectedFactoryCalls); @@ -89,7 +89,7 @@ public async Task FactoryThrowsThenSucceeds_CachesSuccessfulScheduler() // First Schedule call — factory throws, falls back to CurrentThreadScheduler (not cached) ISequencer? firstCallScheduler = null; - sut.Schedule( + _ = sut.Schedule( null!, (scheduler, _) => { @@ -99,7 +99,7 @@ public async Task FactoryThrowsThenSucceeds_CachesSuccessfulScheduler() // Second Schedule call — factory succeeds, result is cached ISequencer? secondCallScheduler = null; - sut.Schedule( + _ = sut.Schedule( null!, (scheduler, _) => { @@ -109,7 +109,7 @@ public async Task FactoryThrowsThenSucceeds_CachesSuccessfulScheduler() // Third Schedule call — uses cached scheduler, factory not called again var callsBeforeThird = schedulerFactoryCalls; - sut.Schedule( + _ = sut.Schedule( null!, (_, _) => Scope.Empty); @@ -127,7 +127,7 @@ public async Task FactoryThrowsInvalidOperationException_FallsBackToCurrentThrea var schedulerFactory = new Func(() => throw new InvalidOperationException()); var sut = new WaitForDispatcherScheduler(schedulerFactory); - sut.Schedule( + _ = sut.Schedule( null!, (scheduler, _) => { @@ -151,7 +151,7 @@ public async Task SuccessfulFactory_UsesCachedScheduler() }); var sut = new WaitForDispatcherScheduler(schedulerFactory); - sut.Schedule(() => { }); + _ = sut.Schedule(() => { }); await Assert.That(schedulerFactoryCalls).IsEqualTo(1); } diff --git a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.ObservableForProperty.cs b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.ObservableForProperty.cs index e0277f02a5..cfced044ef 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.ObservableForProperty.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.ObservableForProperty.cs @@ -36,7 +36,7 @@ public async Task ObservableForProperty_StringPropertyName_ObservesProperty() var results = new List(); - fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord)).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); + _ = fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord)).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); fixture.IsOnlyOneWord = Value1Text; @@ -63,7 +63,7 @@ public async Task ObservableForProperty_StringPropertyNameBeforeChange_ObservesB var results = new List(); - fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord), true).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); + _ = fixture.ObservableForProperty(nameof(TestFixture.IsOnlyOneWord), true).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); fixture.IsOnlyOneWord = ChangedText; @@ -83,7 +83,7 @@ public async Task ObservableForProperty_StringPropertyNameNoSkipInitial_EmitsIni var results = new List(); - fixture.ObservableForProperty( + _ = fixture.ObservableForProperty( nameof(TestFixture.IsOnlyOneWord), false, false).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); @@ -133,7 +133,7 @@ public async Task ObservableForProperty_StringPropertyNameWithIsDistinct_Works() var results = new List(); - fixture.ObservableForProperty( + _ = fixture.ObservableForProperty( nameof(TestFixture.IsOnlyOneWord), false, true, @@ -163,7 +163,7 @@ public async Task ObservableForProperty_WithSelector_TransformsValues() var results = new List(); - fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); + _ = fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); fixture.IsOnlyOneWord = "Hello"; @@ -192,7 +192,7 @@ public async Task ObservableForProperty_WithSelectorAndBeforeChange_TransformsBe var results = new List(); - fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0, true).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); + _ = fixture.ObservableForProperty(x => x.IsOnlyOneWord, value => value?.Length ?? 0, true).ObserveOn(Sequencer.Immediate).Subscribe(results.Add); fixture.IsOnlyOneWord = ChangedText; @@ -592,7 +592,7 @@ public async Task SubscribeToExpressionChain_BasicUsage_NotifiesOnChange() var results = new List(); - fixture.SubscribeToExpressionChain(expression.Body).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); + _ = fixture.SubscribeToExpressionChain(expression.Body).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); fixture.Child.IsOnlyOneWord = "First"; @@ -621,7 +621,7 @@ public async Task SubscribeToExpressionChain_WithBeforeChange_NotifiesBeforeChan var results = new List(); - fixture.SubscribeToExpressionChain(expression.Body, true).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); + _ = fixture.SubscribeToExpressionChain(expression.Body, true).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); fixture.Child.IsOnlyOneWord = ChangedText; @@ -643,7 +643,7 @@ public async Task SubscribeToExpressionChain_WithBeforeChangeAndSkipInitial_Skip var results = new List(); - fixture.SubscribeToExpressionChain( + _ = fixture.SubscribeToExpressionChain( expression.Body, true, true).ObserveOn(Sequencer.Immediate).Subscribe(x => results.Add(x.Value)); @@ -671,7 +671,7 @@ public async Task SubscribeToExpressionChain_WithIsDistinct_Works() var results = new List(); - fixture.SubscribeToExpressionChain( + _ = fixture.SubscribeToExpressionChain( expression.Body, false, true, @@ -701,7 +701,7 @@ public async Task SubscribeToExpressionChain_WithSuppressWarnings_DoesNotWarn() var results = new List(); - fixture.SubscribeToExpressionChain( + _ = fixture.SubscribeToExpressionChain( expression.Body, false, true, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.WhenAnyValue.cs b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.WhenAnyValue.cs index d39c50e6bf..1c4f4dd6f4 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.WhenAnyValue.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.WhenAnyValue.cs @@ -24,7 +24,7 @@ public async Task WhenAnyValueWith10ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -58,7 +58,7 @@ public async Task WhenAnyValueWith11ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -96,7 +96,7 @@ public async Task WhenAnyValueWith12ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -131,7 +131,7 @@ public async Task WhenAnyValueWith1Paramerters() string? result = null; - fixture.WhenAnyValue(x => x.Value1).Subscribe(value => result = value); + _ = fixture.WhenAnyValue(x => x.Value1).Subscribe(value => result = value); await Assert.That(result).IsEqualTo(OneText); } @@ -147,7 +147,7 @@ public async Task WhenAnyValueWith1ParamertersSequentialCheck() fixture.Value1 = null!; - fixture.WhenAnyValue(x => x.Value1).Subscribe(value => result = value); + _ = fixture.WhenAnyValue(x => x.Value1).Subscribe(value => result = value); await Assert.That(result).IsNull(); @@ -173,7 +173,7 @@ public async Task WhenAnyValueWith1ParamertersSequentialCheckNullable() var result = string.Empty; - fixture.WhenAnyValue(x => x.Value2).Subscribe(value => result = value); + _ = fixture.WhenAnyValue(x => x.Value2).Subscribe(value => result = value); await Assert.That(result).IsNull(); @@ -199,7 +199,7 @@ public async Task WhenAnyValueWith2ParamertersReturnsTuple() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2).Select(tuple => { @@ -220,7 +220,7 @@ public async Task WhenAnyValueWith2ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, (v1, v2) => (v1, v2)).Select(tuple => @@ -242,7 +242,7 @@ public async Task WhenAnyValueWith3ParamertersReturnsTuple() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3).Select(tuple => @@ -264,7 +264,7 @@ public async Task WhenAnyValueWith3ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -287,7 +287,7 @@ public async Task WhenAnyValueWith4ParamertersReturnsTuple() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -310,7 +310,7 @@ public async Task WhenAnyValueWith4ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -334,7 +334,7 @@ public async Task WhenAnyValueWith5ParamertersReturnsTuple() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -358,7 +358,7 @@ public async Task WhenAnyValueWith5ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -383,7 +383,7 @@ public async Task WhenAnyValueWith6ParamertersReturnsTuple() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -408,7 +408,7 @@ public async Task WhenAnyValueWith6ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -434,7 +434,7 @@ public async Task WhenAnyValueWith7ParamertersReturnsTuple() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -460,7 +460,7 @@ public async Task WhenAnyValueWith7ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -491,7 +491,7 @@ public async Task WhenAnyValueWith8ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, @@ -523,7 +523,7 @@ public async Task WhenAnyValueWith9ParamertersReturnsValues() string? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.Value1, x => x.Value2, x => x.Value3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs index 9e372ae1ed..70352918b4 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/ReactiveNotifyPropertyChangedMixinTest.cs @@ -99,7 +99,7 @@ public async Task AnyChangeInExpressionListTriggersUpdate() var obsUpdated = false; - obj.ObservableForProperty(x => x.Model.Model.Model.SomeOtherParam).Subscribe(_ => obsUpdated = true); + _ = obj.ObservableForProperty(x => x.Model.Model.Model.SomeOtherParam).Subscribe(_ => obsUpdated = true); obsUpdated = false; @@ -137,7 +137,7 @@ public async Task ChangedShouldHaveValidData() string? propertyName = null; - fixture.Changed.ObserveOn(Sequencer.Immediate).Subscribe(x => + _ = fixture.Changed.ObserveOn(Sequencer.Immediate).Subscribe(x => { sender = x.Sender; @@ -178,7 +178,7 @@ public async Task ChangingShouldHaveValidData() string? propertyName = null; - fixture.Changing.ObserveOn(Sequencer.Immediate).Subscribe(x => + _ = fixture.Changing.ObserveOn(Sequencer.Immediate).Subscribe(x => { sender = x.Sender; @@ -240,10 +240,10 @@ public async Task MultiPropertyExpressionsShouldBeProperlyResolved() } }; - var results = data.Keys.Select(static x => new { input = x, output = Reflection.Rewrite(x.Body).GetExpressionChain() }).ToArray(); + var results = data.Keys.Select(static x => (input: x, output: Reflection.Rewrite(x.Body).GetExpressionChain())).ToArray(); var resultTypes = dataTypes.Keys.Select(static x => - new { input = x, output = Reflection.Rewrite(x.Body).GetExpressionChain() }).ToArray(); + (input: x, output: Reflection.Rewrite(x.Body).GetExpressionChain())).ToArray(); foreach (var x in results) { @@ -273,7 +273,7 @@ public async Task NonNullableTypesTestShouldntNeedDecorators() IEnumerable? result = null; - fixture.WhenAnyValue(x => x.AccountService.AccountUsers) + _ = fixture.WhenAnyValue(x => x.AccountService.AccountUsers) .Where(users => users.Count > 0) .Select(users => users.Values.Where(x => !string.IsNullOrWhiteSpace(x.LastName))) .Subscribe(dict => result = dict); @@ -292,7 +292,7 @@ public async Task NonNullableTypesTestShouldntNeedDecorators2() IEnumerable? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.ProjectService.Projects, x => x.AccountService.AccountUsers).Where(tuple => tuple.Value1?.Count > 0 && tuple.Value2?.Count > 0).Select(tuple => { @@ -315,7 +315,7 @@ public async Task NullableTypesTestShouldntNeedDecorators() IEnumerable? result = null; - fixture.WhenAnyValue(x => x.AccountService.AccountUsersNullable) + _ = fixture.WhenAnyValue(x => x.AccountService.AccountUsersNullable) .Where(users => users.Count > 0) .Select(users => users.Values.Where(x => !string.IsNullOrWhiteSpace(x?.LastName))) .Subscribe(dict => result = dict); @@ -334,7 +334,7 @@ public async Task NullableTypesTestShouldntNeedDecorators2() IEnumerable? result = null; - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.ProjectService.ProjectsNullable, x => x.AccountService.AccountUsersNullable).Where(tuple => tuple.Value1.Count > 0 && tuple.Value2?.Count > 0).Select(tuple => { @@ -357,7 +357,7 @@ public async Task ObjectShouldBeGarbageCollectedWhenPropertyValueChanges() var weakRef = new WeakReference(obj.Model); - obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); + _ = obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); obj.Model = new(); @@ -370,7 +370,7 @@ public async Task ObjectShouldBeGarbageCollectedWhenPropertyValueChanges() var weakRef = new WeakReference(obj.Model.Model); - obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); + _ = obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); obj.Model.Model = new(); @@ -383,7 +383,7 @@ public async Task ObjectShouldBeGarbageCollectedWhenPropertyValueChanges() var weakRef = new WeakReference(obj.Model.Model.Model); - obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); + _ = obj.ObservableForProperty(static x => x.Model.Model.Model.SomeOtherParam).Subscribe(); obj.Model.Model.Model = new(); @@ -428,7 +428,7 @@ public async Task SubscriptionToWhenAnyShouldReturnCurrentValue() var observedValue = 1; - obj.WhenAnyValue(x => x.SomeOtherParam).Subscribe(x => observedValue = x); + _ = obj.WhenAnyValue(x => x.SomeOtherParam).Subscribe(x => observedValue = x); obj.SomeOtherParam = ExpectedValue; @@ -445,7 +445,7 @@ public async Task WhenAnyShouldRunInContext() var fixture = new TestFixture { IsNotNullString = FooText, IsOnlyOneWord = BazText, PocoProperty = BamfText }; - fixture.WhenAnyValue(x => x.IsNotNullString).ObserveOn(Sequencer.Immediate).Subscribe(__ => whenAnyTid = Environment.CurrentManagedThreadId); + _ = fixture.WhenAnyValue(x => x.IsNotNullString).ObserveOn(Sequencer.Immediate).Subscribe(__ => whenAnyTid = Environment.CurrentManagedThreadId); fixture.IsNotNullString = BarText; @@ -461,23 +461,23 @@ public async Task WhenAnyShouldWorkEvenWithNormalProperties() var output = new List?>(); - fixture.WhenAny( + _ = fixture.WhenAny( static x => x.PocoProperty, static x => x).Subscribe(output.Add); var output2 = new List(); - fixture.WhenAnyValue(static x => x.PocoProperty).Subscribe(output2.Add); + _ = fixture.WhenAnyValue(static x => x.PocoProperty).Subscribe(output2.Add); var output3 = new List?>(); - fixture.WhenAny( + _ = fixture.WhenAny( static x => x.NullableInt, static x => x).Subscribe(output3.Add); var output4 = new List(); - fixture.WhenAnyValue(static x => x.NullableInt).Subscribe(output4.Add); + _ = fixture.WhenAnyValue(static x => x.NullableInt).Subscribe(output4.Add); using (Assert.Multiple()) { @@ -525,10 +525,10 @@ public async Task WhenAnySmokeTest() var output2 = new List>(); - fixture.WhenAny( + _ = fixture.WhenAny( x => x.SomeOtherParam, x => x.Child!.IsNotNullString, - (sop, nns) => new { sop, nns }).Subscribe(x => + (sop, nns) => (sop, nns)).Subscribe(x => { output1.Add(x.sop); @@ -601,9 +601,9 @@ public async Task WhenAnyValueShouldWorkEvenWithNormalProperties() var output2 = new List(); - fixture.WhenAnyValue(static x => x.PocoProperty).Subscribe(output1.Add); + _ = fixture.WhenAnyValue(static x => x.PocoProperty).Subscribe(output1.Add); - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( static x => x.IsOnlyOneWord, static x => x?.Length).Subscribe(output2.Add); @@ -637,10 +637,10 @@ public async Task WhenAnyValueSmokeTest() var output2 = new List(); - fixture.WhenAnyValue( + _ = fixture.WhenAnyValue( x => x.SomeOtherParam, x => x.Child!.IsNotNullString, - (sop, nns) => new { sop, nns }).Subscribe(x => + (sop, nns) => (sop, nns)).Subscribe(x => { output1.Add(x.sop); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity1.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity1.cs index d3386aeef2..bde830d0a1 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity1.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity1.cs @@ -15,7 +15,7 @@ public async Task WhenAny_1Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, _ => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); @@ -28,7 +28,7 @@ public async Task WhenAny_1Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, _ => "x", true).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -42,7 +42,7 @@ public async Task WhenAny_1Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), _ => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); @@ -55,7 +55,7 @@ public async Task WhenAny_1Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), _ => "x", false).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -69,7 +69,7 @@ public async Task WhenAnyValue_1Prop_Expr() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue(x => x.Property1).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = vm.WhenAnyValue(x => x.Property1).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); vm.Property1 = "a"; await Assert.That(list).Count().IsGreaterThan(1); @@ -82,7 +82,7 @@ public async Task WhenAnyValue_1Prop_Expr_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue(x => x.Property1, true).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = vm.WhenAnyValue(x => x.Property1, true).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } @@ -93,7 +93,7 @@ public async Task WhenAnyValue_1Prop_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue(nameof(WhenAnyArityTestViewModel.Property1)).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = vm.WhenAnyValue(nameof(WhenAnyArityTestViewModel.Property1)).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } @@ -104,7 +104,7 @@ public async Task WhenAnyValue_1Prop_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue(nameof(WhenAnyArityTestViewModel.Property1), false).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = vm.WhenAnyValue(nameof(WhenAnyArityTestViewModel.Property1), false).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } @@ -116,7 +116,7 @@ public async Task WhenAnyValue_1Props_Sel() var vm = new WhenAnyArityTestViewModel(); var list = new List(); Func selector = _ => "x"; - vm.WhenAnyValue(x => x.Property1, selector).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = vm.WhenAnyValue(x => x.Property1, selector).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } @@ -128,7 +128,7 @@ public async Task WhenAnyValue_1Props_Sel_Dist() var vm = new WhenAnyArityTestViewModel(); var list = new List(); Func selector = _ => "x"; - vm.WhenAnyValue(x => x.Property1, selector, true).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = vm.WhenAnyValue(x => x.Property1, selector, true).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); } @@ -139,7 +139,7 @@ public async Task WhenAnyValue_1Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), _ => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); @@ -152,7 +152,7 @@ public async Task WhenAnyValue_1Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), _ => "x", false).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity10.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity10.cs index 3a9859d7e4..95aeb56811 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity10.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity10.cs @@ -21,7 +21,7 @@ public async Task WhenAny_10Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -43,7 +43,7 @@ public async Task WhenAny_10Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -66,7 +66,7 @@ public async Task WhenAny_10Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -88,7 +88,7 @@ public async Task WhenAny_10Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -111,7 +111,7 @@ public async Task WhenAnyValue_10Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -133,7 +133,7 @@ public async Task WhenAnyValue_10Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -156,7 +156,7 @@ public async Task WhenAnyValue_10Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -178,7 +178,7 @@ public async Task WhenAnyValue_10Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity11.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity11.cs index 320bd12459..7d38b699a4 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity11.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity11.cs @@ -21,7 +21,7 @@ public async Task WhenAny_11Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -44,7 +44,7 @@ public async Task WhenAny_11Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -68,7 +68,7 @@ public async Task WhenAny_11Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -91,7 +91,7 @@ public async Task WhenAny_11Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -115,7 +115,7 @@ public async Task WhenAnyValue_11Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -138,7 +138,7 @@ public async Task WhenAnyValue_11Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -162,7 +162,7 @@ public async Task WhenAnyValue_11Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -185,7 +185,7 @@ public async Task WhenAnyValue_11Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity12.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity12.cs index d42d7a67ec..5070b477bb 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity12.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity12.cs @@ -21,7 +21,7 @@ public async Task WhenAny_12Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -45,7 +45,7 @@ public async Task WhenAny_12Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -70,7 +70,7 @@ public async Task WhenAny_12Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -94,7 +94,7 @@ public async Task WhenAny_12Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -119,7 +119,7 @@ public async Task WhenAnyValue_12Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -143,7 +143,7 @@ public async Task WhenAnyValue_12Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -168,7 +168,7 @@ public async Task WhenAnyValue_12Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -192,7 +192,7 @@ public async Task WhenAnyValue_12Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity2.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity2.cs index 6ec8fa3c22..63ea6b944d 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity2.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity2.cs @@ -15,7 +15,7 @@ public async Task WhenAny_2Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, (_, _) => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -29,7 +29,7 @@ public async Task WhenAny_2Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, (_, _) => "x", @@ -44,7 +44,7 @@ public async Task WhenAny_2Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), (_, _) => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -58,7 +58,7 @@ public async Task WhenAny_2Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), (_, _) => "x", @@ -73,7 +73,7 @@ public async Task WhenAnyValue_2Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, (_, _) => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -87,7 +87,7 @@ public async Task WhenAnyValue_2Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, (_, _) => "x", @@ -102,7 +102,7 @@ public async Task WhenAnyValue_2Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), (_, _) => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -116,7 +116,7 @@ public async Task WhenAnyValue_2Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), (_, _) => "x", @@ -131,7 +131,7 @@ public async Task WhenAnyValue_2Props_Tuple_Expr() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); @@ -144,7 +144,7 @@ public async Task WhenAnyValue_2Props_Tuple_Expr_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, true).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -158,7 +158,7 @@ public async Task WhenAnyValue_2Props_Tuple_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2)).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).Count().IsGreaterThan(0); @@ -171,7 +171,7 @@ public async Task WhenAnyValue_2Props_Tuple_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), false).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity3.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity3.cs index 7e1d9ead2c..337224cf34 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity3.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity3.cs @@ -15,7 +15,7 @@ public async Task WhenAny_3Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -30,7 +30,7 @@ public async Task WhenAny_3Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -46,7 +46,7 @@ public async Task WhenAny_3Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -61,7 +61,7 @@ public async Task WhenAny_3Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -77,7 +77,7 @@ public async Task WhenAnyValue_3Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -92,7 +92,7 @@ public async Task WhenAnyValue_3Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -108,7 +108,7 @@ public async Task WhenAnyValue_3Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -123,7 +123,7 @@ public async Task WhenAnyValue_3Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -139,7 +139,7 @@ public async Task WhenAnyValue_3Props_Tuple_Expr() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -153,7 +153,7 @@ public async Task WhenAnyValue_3Props_Tuple_Expr_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -168,7 +168,7 @@ public async Task WhenAnyValue_3Props_Tuple_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3)).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -182,7 +182,7 @@ public async Task WhenAnyValue_3Props_Tuple_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity4.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity4.cs index 42e13de856..4fc7316c10 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity4.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity4.cs @@ -15,7 +15,7 @@ public async Task WhenAny_4Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -31,7 +31,7 @@ public async Task WhenAny_4Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -48,7 +48,7 @@ public async Task WhenAny_4Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -64,7 +64,7 @@ public async Task WhenAny_4Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -81,7 +81,7 @@ public async Task WhenAnyValue_4Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -97,7 +97,7 @@ public async Task WhenAnyValue_4Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -114,7 +114,7 @@ public async Task WhenAnyValue_4Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -130,7 +130,7 @@ public async Task WhenAnyValue_4Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -147,7 +147,7 @@ public async Task WhenAnyValue_4Props_Tuple_Expr() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -162,7 +162,7 @@ public async Task WhenAnyValue_4Props_Tuple_Expr_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -178,7 +178,7 @@ public async Task WhenAnyValue_4Props_Tuple_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -193,7 +193,7 @@ public async Task WhenAnyValue_4Props_Tuple_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity5.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity5.cs index 7731c5dc42..c7ac04a4c9 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity5.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity5.cs @@ -15,7 +15,7 @@ public async Task WhenAny_5Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -32,7 +32,7 @@ public async Task WhenAny_5Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -50,7 +50,7 @@ public async Task WhenAny_5Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -67,7 +67,7 @@ public async Task WhenAny_5Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -85,7 +85,7 @@ public async Task WhenAnyValue_5Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -102,7 +102,7 @@ public async Task WhenAnyValue_5Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -120,7 +120,7 @@ public async Task WhenAnyValue_5Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -137,7 +137,7 @@ public async Task WhenAnyValue_5Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -155,7 +155,7 @@ public async Task WhenAnyValue_5Props_Tuple_Expr() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -171,7 +171,7 @@ public async Task WhenAnyValue_5Props_Tuple_Expr_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -188,7 +188,7 @@ public async Task WhenAnyValue_5Props_Tuple_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -204,7 +204,7 @@ public async Task WhenAnyValue_5Props_Tuple_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity6.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity6.cs index 706eb81fe1..d959ac2be5 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity6.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity6.cs @@ -15,7 +15,7 @@ public async Task WhenAny_6Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -33,7 +33,7 @@ public async Task WhenAny_6Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -52,7 +52,7 @@ public async Task WhenAny_6Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -70,7 +70,7 @@ public async Task WhenAny_6Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -89,7 +89,7 @@ public async Task WhenAnyValue_6Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -107,7 +107,7 @@ public async Task WhenAnyValue_6Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -126,7 +126,7 @@ public async Task WhenAnyValue_6Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -144,7 +144,7 @@ public async Task WhenAnyValue_6Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -163,7 +163,7 @@ public async Task WhenAnyValue_6Props_Tuple_Expr() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -180,7 +180,7 @@ public async Task WhenAnyValue_6Props_Tuple_Expr_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -198,7 +198,7 @@ public async Task WhenAnyValue_6Props_Tuple_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -215,7 +215,7 @@ public async Task WhenAnyValue_6Props_Tuple_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity7.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity7.cs index 9abc86c866..b2f16ce364 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity7.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity7.cs @@ -15,7 +15,7 @@ public async Task WhenAny_7Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -34,7 +34,7 @@ public async Task WhenAny_7Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -54,7 +54,7 @@ public async Task WhenAny_7Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -73,7 +73,7 @@ public async Task WhenAny_7Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -93,7 +93,7 @@ public async Task WhenAnyValue_7Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -112,7 +112,7 @@ public async Task WhenAnyValue_7Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -132,7 +132,7 @@ public async Task WhenAnyValue_7Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -151,7 +151,7 @@ public async Task WhenAnyValue_7Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -171,7 +171,7 @@ public async Task WhenAnyValue_7Props_Tuple_Expr() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -189,7 +189,7 @@ public async Task WhenAnyValue_7Props_Tuple_Expr_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -208,7 +208,7 @@ public async Task WhenAnyValue_7Props_Tuple_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -226,7 +226,7 @@ public async Task WhenAnyValue_7Props_Tuple_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List<(string?, string?, string?, string?, string?, string?, string?)>(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity8.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity8.cs index 2a255aa5b5..e8d826fc71 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity8.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity8.cs @@ -21,7 +21,7 @@ public async Task WhenAny_8Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -41,7 +41,7 @@ public async Task WhenAny_8Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -62,7 +62,7 @@ public async Task WhenAny_8Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -82,7 +82,7 @@ public async Task WhenAny_8Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -103,7 +103,7 @@ public async Task WhenAnyValue_8Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -123,7 +123,7 @@ public async Task WhenAnyValue_8Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -144,7 +144,7 @@ public async Task WhenAnyValue_8Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -164,7 +164,7 @@ public async Task WhenAnyValue_8Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity9.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity9.cs index 9f6c9e531a..8c99bb9d5f 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity9.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyMixinTests.Arity9.cs @@ -21,7 +21,7 @@ public async Task WhenAny_9Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -42,7 +42,7 @@ public async Task WhenAny_9Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( x => x.Property1, x => x.Property2, x => x.Property3, @@ -64,7 +64,7 @@ public async Task WhenAny_9Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -85,7 +85,7 @@ public async Task WhenAny_9Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAny( + _ = vm.WhenAny( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -107,7 +107,7 @@ public async Task WhenAnyValue_9Props_Sel() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -128,7 +128,7 @@ public async Task WhenAnyValue_9Props_Sel_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( x => x.Property1, x => x.Property2, x => x.Property3, @@ -150,7 +150,7 @@ public async Task WhenAnyValue_9Props_Sel_Str() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), @@ -171,7 +171,7 @@ public async Task WhenAnyValue_9Props_Sel_Str_Dist() { var vm = new WhenAnyArityTestViewModel(); var list = new List(); - vm.WhenAnyValue( + _ = vm.WhenAnyValue( nameof(WhenAnyArityTestViewModel.Property1), nameof(WhenAnyArityTestViewModel.Property2), nameof(WhenAnyArityTestViewModel.Property3), diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity1.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity1.cs index df66fee2ed..b9ef9ef9bf 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity1.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity1.cs @@ -17,7 +17,7 @@ public async Task WhenAnyObservable_1Prop() var subj = new Signal(); vm.ObservableProperty1 = subj; var list = new List(); - vm.WhenAnyObservable(x => x.ObservableProperty1).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = vm.WhenAnyObservable(x => x.ObservableProperty1).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); subj.OnNext("test"); await Assert.That(list).Count().IsGreaterThan(0); } diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity10.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity10.cs index c436b77aae..42df88fe62 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity10.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity10.cs @@ -41,7 +41,7 @@ public async Task WhenAnyObservable_10Props() var subj10 = new Signal(); vm.ObservableProperty10 = subj10; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -92,7 +92,7 @@ public async Task WhenAnyObservable_10Props_Sel() var subj10 = new Signal(); vm.ObservableProperty10 = subj10; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity11.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity11.cs index ebc823112f..42a0011015 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity11.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity11.cs @@ -43,7 +43,7 @@ public async Task WhenAnyObservable_11Props() var subj11 = new Signal(); vm.ObservableProperty11 = subj11; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -98,7 +98,7 @@ public async Task WhenAnyObservable_11Props_Sel() var subj11 = new Signal(); vm.ObservableProperty11 = subj11; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity12.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity12.cs index 4f34c01f6d..c10041c26a 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity12.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity12.cs @@ -45,7 +45,7 @@ public async Task WhenAnyObservable_12Props() var subj12 = new Signal(); vm.ObservableProperty12 = subj12; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -104,7 +104,7 @@ public async Task WhenAnyObservable_12Props_Sel() var subj12 = new Signal(); vm.ObservableProperty12 = subj12; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity2.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity2.cs index f8241957a6..db4cda959f 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity2.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity2.cs @@ -19,7 +19,7 @@ public async Task WhenAnyObservable_2Props() var subj2 = new Signal(); vm.ObservableProperty2 = subj2; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); subj1.OnNext("test"); @@ -38,7 +38,7 @@ public async Task WhenAnyObservable_2Props_Sel() var subj2 = new Signal(); vm.ObservableProperty2 = subj2; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, (_, _) => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity3.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity3.cs index 79280f8837..08df3ebda7 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity3.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity3.cs @@ -21,7 +21,7 @@ public async Task WhenAnyObservable_3Props() var subj3 = new Signal(); vm.ObservableProperty3 = subj3; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -44,7 +44,7 @@ public async Task WhenAnyObservable_3Props_Sel() var subj3 = new Signal(); vm.ObservableProperty3 = subj3; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity4.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity4.cs index cc3aff14bf..01a1212ced 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity4.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity4.cs @@ -23,7 +23,7 @@ public async Task WhenAnyObservable_4Props() var subj4 = new Signal(); vm.ObservableProperty4 = subj4; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -50,7 +50,7 @@ public async Task WhenAnyObservable_4Props_Sel() var subj4 = new Signal(); vm.ObservableProperty4 = subj4; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity5.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity5.cs index 7387e89a89..bf5980f429 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity5.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity5.cs @@ -25,7 +25,7 @@ public async Task WhenAnyObservable_5Props() var subj5 = new Signal(); vm.ObservableProperty5 = subj5; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -56,7 +56,7 @@ public async Task WhenAnyObservable_5Props_Sel() var subj5 = new Signal(); vm.ObservableProperty5 = subj5; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity6.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity6.cs index 52afeaa09a..f5df290b0d 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity6.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity6.cs @@ -27,7 +27,7 @@ public async Task WhenAnyObservable_6Props() var subj6 = new Signal(); vm.ObservableProperty6 = subj6; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -62,7 +62,7 @@ public async Task WhenAnyObservable_6Props_Sel() var subj6 = new Signal(); vm.ObservableProperty6 = subj6; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity7.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity7.cs index 3b808094e2..3470ea97a0 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity7.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity7.cs @@ -29,7 +29,7 @@ public async Task WhenAnyObservable_7Props() var subj7 = new Signal(); vm.ObservableProperty7 = subj7; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -68,7 +68,7 @@ public async Task WhenAnyObservable_7Props_Sel() var subj7 = new Signal(); vm.ObservableProperty7 = subj7; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity8.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity8.cs index d690eb5ea6..e3fb703f88 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity8.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity8.cs @@ -37,7 +37,7 @@ public async Task WhenAnyObservable_8Props() var subj8 = new Signal(); vm.ObservableProperty8 = subj8; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -80,7 +80,7 @@ public async Task WhenAnyObservable_8Props_Sel() var subj8 = new Signal(); vm.ObservableProperty8 = subj8; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity9.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity9.cs index 8f09a94af9..13139706d4 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity9.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableMixinTests.Arity9.cs @@ -39,7 +39,7 @@ public async Task WhenAnyObservable_9Props() var subj9 = new Signal(); vm.ObservableProperty9 = subj9; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, @@ -86,7 +86,7 @@ public async Task WhenAnyObservable_9Props_Sel() var subj9 = new Signal(); vm.ObservableProperty9 = subj9; var list = new List(); - vm.WhenAnyObservable( + _ = vm.WhenAnyObservable( x => x.ObservableProperty1, x => x.ObservableProperty2, x => x.ObservableProperty3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableTests.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableTests.cs index 3ccbb7daa1..b5b9117c06 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableTests.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnyObservableTests.cs @@ -43,7 +43,7 @@ public async Task WhenAnyObservableSmokeTestCombining() var fixture = new TestWhenAnyObsViewModel(); var list = new List(); - fixture.WhenAnyObservable(static x => x.Command3, static x => x.Command1, static (s, i) => s + " : " + i).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = fixture.WhenAnyObservable(static x => x.Command3, static x => x.Command1, static (s, i) => s + " : " + i).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).IsEmpty(); await fixture.Command1!.Execute(1); @@ -61,7 +61,7 @@ public async Task WhenAnyObservableSmokeTestCombining() await Assert.That( ExpectedCombiningResults.Zip( list, - static (expected, actual) => new { expected, actual }).All(static x => x.expected == x.actual)).IsTrue(); + static (expected, actual) => (expected, actual)).All(static x => x.expected == x.actual)).IsTrue(); } } @@ -76,7 +76,7 @@ public async Task WhenAnyObservableSmokeTestMerging() var fixture = new TestWhenAnyObsViewModel(); var list = new List(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command2).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); + _ = fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command2).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); await Assert.That(list).IsEmpty(); await fixture.Command1!.Execute(1); @@ -93,7 +93,7 @@ public async Task WhenAnyObservableSmokeTestMerging() await Assert.That( new[] { 1, SecondCommandValue, 1 }.Zip( list, - static (expected, actual) => new { expected, actual }).All(static x => x.expected == x.actual)).IsTrue(); + static (expected, actual) => (expected, actual)).All(static x => x.expected == x.actual)).IsTrue(); } } @@ -102,21 +102,21 @@ await Assert.That( private static void SubscribeToNullLowArityMergeOverloads(TestWhenAnyObsViewModel fixture) { // these are the lower-arity overloads of WhenAnyObservable that perform a Merge - fixture.WhenAnyObservable(static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable(static x => x.Command1).Subscribe(); + _ = fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1).Subscribe(); + _ = fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -130,7 +130,7 @@ private static void SubscribeToNullLowArityMergeOverloads(TestWhenAnyObsViewMode private static void SubscribeToNullHighArityMergeOverloads(TestWhenAnyObsViewModel fixture) { // these are the higher-arity overloads of WhenAnyObservable that perform a Merge - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -138,7 +138,7 @@ private static void SubscribeToNullHighArityMergeOverloads(TestWhenAnyObsViewMod static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -147,7 +147,7 @@ private static void SubscribeToNullHighArityMergeOverloads(TestWhenAnyObsViewMod static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -157,7 +157,7 @@ private static void SubscribeToNullHighArityMergeOverloads(TestWhenAnyObsViewMod static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -168,7 +168,7 @@ private static void SubscribeToNullHighArityMergeOverloads(TestWhenAnyObsViewMod static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -180,7 +180,7 @@ private static void SubscribeToNullHighArityMergeOverloads(TestWhenAnyObsViewMod static x => x.Command1, static x => x.Command1, static x => x.Command1).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -204,26 +204,26 @@ private static void SubscribeToNullHighArityMergeOverloads(TestWhenAnyObsViewMod private static void SubscribeToNullLowArityCombineLatestOverloads(TestWhenAnyObsViewModel fixture) { // these are the lower-arity overloads of WhenAnyObservable that perform a CombineLatest - fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static (_, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable(static x => x.Command1, static x => x.Command1, static (_, _) => RxVoid.Default).Subscribe(); + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, static (_, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (_, _, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static x => x.Command1, static (_, _, _, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -231,7 +231,7 @@ private static void SubscribeToNullLowArityCombineLatestOverloads(TestWhenAnyObs static x => x.Command1, static x => x.Command1, static (_, _, _, _, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -251,7 +251,7 @@ private static void SubscribeToNullLowArityCombineLatestOverloads(TestWhenAnyObs private static void SubscribeToNullHighArityCombineLatestOverloads(TestWhenAnyObsViewModel fixture) { // these are the higher-arity overloads of WhenAnyObservable that perform a CombineLatest - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -261,7 +261,7 @@ private static void SubscribeToNullHighArityCombineLatestOverloads(TestWhenAnyOb static x => x.Command1, static x => x.Command1, static (_, _, _, _, _, _, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -272,7 +272,7 @@ private static void SubscribeToNullHighArityCombineLatestOverloads(TestWhenAnyOb static x => x.Command1, static x => x.Command1, static (_, _, _, _, _, _, _, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -284,7 +284,7 @@ private static void SubscribeToNullHighArityCombineLatestOverloads(TestWhenAnyOb static x => x.Command1, static x => x.Command1, static (_, _, _, _, _, _, _, _, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, @@ -297,7 +297,7 @@ private static void SubscribeToNullHighArityCombineLatestOverloads(TestWhenAnyOb static x => x.Command1, static x => x.Command1, static (_, _, _, _, _, _, _, _, _, _, _) => RxVoid.Default).Subscribe(); - fixture.WhenAnyObservable( + _ = fixture.WhenAnyObservable( static x => x.Command1, static x => x.Command1, static x => x.Command1, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change10.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change10.cs index 2be783baed..28c4d334dc 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change10.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change10.cs @@ -83,7 +83,7 @@ public async Task ChangeSink10_ForwardsError() var e9 = new Signal(); var e10 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( e1, e2, e3, @@ -116,7 +116,7 @@ public async Task ChangeSink10_Completes() var k9 = new Signal(); var k10 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( k1, k2, k3, @@ -158,7 +158,7 @@ public async Task ChangeSink10_SelectorThrows() var t9 = new Signal(); var t10 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change11.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change11.cs index a5b18cb81c..3c3ee2eb31 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change11.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change11.cs @@ -88,7 +88,7 @@ public async Task ChangeSink11_ForwardsError() var e10 = new Signal(); var e11 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( e1, e2, e3, @@ -123,7 +123,7 @@ public async Task ChangeSink11_Completes() var k10 = new Signal(); var k11 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( k1, k2, k3, @@ -168,7 +168,7 @@ public async Task ChangeSink11_SelectorThrows() var t10 = new Signal(); var t11 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change12.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change12.cs index dcd8e9f661..719b81d932 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change12.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change12.cs @@ -93,7 +93,7 @@ public async Task ChangeSink12_ForwardsError() var e11 = new Signal(); var e12 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( e1, e2, e3, @@ -130,7 +130,7 @@ public async Task ChangeSink12_Completes() var k11 = new Signal(); var k12 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( k1, k2, k3, @@ -178,7 +178,7 @@ public async Task ChangeSink12_SelectorThrows() var t11 = new Signal(); var t12 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change2.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change2.cs index d47350291b..5c9b26cc41 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change2.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change2.cs @@ -32,20 +32,20 @@ public async Task ChangeSink2_AllPaths() var e1 = new Signal(); var e2 = new Signal(); var errRec = new Recorder(); - new WhenAnyChangeSink(e1, e2, (x1, x2) => x1 + x2).Subscribe(errRec); + _ = new WhenAnyChangeSink(e1, e2, (x1, x2) => x1 + x2).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal(); var k2 = new Signal(); var cmpRec = new Recorder(); - new WhenAnyChangeSink(k1, k2, (x1, x2) => x1 + x2).Subscribe(cmpRec); + _ = new WhenAnyChangeSink(k1, k2, (x1, x2) => x1 + x2).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); var t1 = new Signal(); var t2 = new Signal(); var throwRec = new Recorder(); - new WhenAnyChangeSink(t1, t2, (_, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyChangeSink(t1, t2, (_, _) => throw ex).Subscribe(throwRec); t1.OnNext("a"); t2.OnNext("a"); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change3.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change3.cs index 19b23c9d9c..7191d75af2 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change3.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change3.cs @@ -36,14 +36,14 @@ public async Task ChangeSink3_AllPaths() var e2 = new Signal(); var e3 = new Signal(); var errRec = new Recorder(); - new WhenAnyChangeSink(e1, e2, e3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(errRec); + _ = new WhenAnyChangeSink(e1, e2, e3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal(); var k2 = new Signal(); var k3 = new Signal(); var cmpRec = new Recorder(); - new WhenAnyChangeSink(k1, k2, k3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(cmpRec); + _ = new WhenAnyChangeSink(k1, k2, k3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -52,7 +52,7 @@ public async Task ChangeSink3_AllPaths() var t2 = new Signal(); var t3 = new Signal(); var throwRec = new Recorder(); - new WhenAnyChangeSink(t1, t2, t3, (_, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyChangeSink(t1, t2, t3, (_, _, _) => throw ex).Subscribe(throwRec); t1.OnNext("a"); t2.OnNext("a"); t3.OnNext("a"); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change4.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change4.cs index 88ac099276..658bc2166b 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change4.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change4.cs @@ -40,7 +40,7 @@ public async Task ChangeSink4_AllPaths() var e3 = new Signal(); var e4 = new Signal(); var errRec = new Recorder(); - new WhenAnyChangeSink(e1, e2, e3, e4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(errRec); + _ = new WhenAnyChangeSink(e1, e2, e3, e4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal(); @@ -48,7 +48,7 @@ public async Task ChangeSink4_AllPaths() var k3 = new Signal(); var k4 = new Signal(); var cmpRec = new Recorder(); - new WhenAnyChangeSink(k1, k2, k3, k4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(cmpRec); + _ = new WhenAnyChangeSink(k1, k2, k3, k4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -59,7 +59,7 @@ public async Task ChangeSink4_AllPaths() var t3 = new Signal(); var t4 = new Signal(); var throwRec = new Recorder(); - new WhenAnyChangeSink(t1, t2, t3, t4, (_, _, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyChangeSink(t1, t2, t3, t4, (_, _, _, _) => throw ex).Subscribe(throwRec); t1.OnNext("a"); t2.OnNext("a"); t3.OnNext("a"); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change5.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change5.cs index 51c343911f..0a8044c582 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change5.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change5.cs @@ -44,7 +44,7 @@ public async Task ChangeSink5_AllPaths() var e4 = new Signal(); var e5 = new Signal(); var errRec = new Recorder(); - new WhenAnyChangeSink(e1, e2, e3, e4, e5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(errRec); + _ = new WhenAnyChangeSink(e1, e2, e3, e4, e5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal(); @@ -53,7 +53,7 @@ public async Task ChangeSink5_AllPaths() var k4 = new Signal(); var k5 = new Signal(); var cmpRec = new Recorder(); - new WhenAnyChangeSink(k1, k2, k3, k4, k5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(cmpRec); + _ = new WhenAnyChangeSink(k1, k2, k3, k4, k5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -66,7 +66,7 @@ public async Task ChangeSink5_AllPaths() var t4 = new Signal(); var t5 = new Signal(); var throwRec = new Recorder(); - new WhenAnyChangeSink(t1, t2, t3, t4, t5, (_, _, _, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyChangeSink(t1, t2, t3, t4, t5, (_, _, _, _, _) => throw ex).Subscribe(throwRec); t1.OnNext("a"); t2.OnNext("a"); t3.OnNext("a"); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change6.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change6.cs index cf044a54d2..ebcfed609a 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change6.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change6.cs @@ -48,7 +48,7 @@ public async Task ChangeSink6_AllPaths() var e5 = new Signal(); var e6 = new Signal(); var errRec = new Recorder(); - new WhenAnyChangeSink(e1, e2, e3, e4, e5, e6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(errRec); + _ = new WhenAnyChangeSink(e1, e2, e3, e4, e5, e6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal(); @@ -58,7 +58,7 @@ public async Task ChangeSink6_AllPaths() var k5 = new Signal(); var k6 = new Signal(); var cmpRec = new Recorder(); - new WhenAnyChangeSink(k1, k2, k3, k4, k5, k6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(cmpRec); + _ = new WhenAnyChangeSink(k1, k2, k3, k4, k5, k6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -73,7 +73,7 @@ public async Task ChangeSink6_AllPaths() var t5 = new Signal(); var t6 = new Signal(); var throwRec = new Recorder(); - new WhenAnyChangeSink(t1, t2, t3, t4, t5, t6, (_, _, _, _, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyChangeSink(t1, t2, t3, t4, t5, t6, (_, _, _, _, _, _) => throw ex).Subscribe(throwRec); t1.OnNext("a"); t2.OnNext("a"); t3.OnNext("a"); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change7.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change7.cs index f6a06134ed..8d793ce188 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change7.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change7.cs @@ -65,7 +65,7 @@ public async Task ChangeSink7_ForwardsError() var e6 = new Signal(); var e7 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( e1, e2, e3, @@ -92,7 +92,7 @@ public async Task ChangeSink7_Completes() var k6 = new Signal(); var k7 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( k1, k2, k3, @@ -125,7 +125,7 @@ public async Task ChangeSink7_SelectorThrows() var t6 = new Signal(); var t7 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change8.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change8.cs index 01440d7b58..dfecd84ccf 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change8.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change8.cs @@ -73,7 +73,7 @@ public async Task ChangeSink8_ForwardsError() var e7 = new Signal(); var e8 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( e1, e2, e3, @@ -102,7 +102,7 @@ public async Task ChangeSink8_Completes() var k7 = new Signal(); var k8 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( k1, k2, k3, @@ -138,7 +138,7 @@ public async Task ChangeSink8_SelectorThrows() var t7 = new Signal(); var t8 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change9.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change9.cs index 61ee912654..0bf2fd48c7 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change9.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Change9.cs @@ -78,7 +78,7 @@ public async Task ChangeSink9_ForwardsError() var e8 = new Signal(); var e9 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( e1, e2, e3, @@ -109,7 +109,7 @@ public async Task ChangeSink9_Completes() var k8 = new Signal(); var k9 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( k1, k2, k3, @@ -148,7 +148,7 @@ public async Task ChangeSink9_SelectorThrows() var t8 = new Signal(); var t9 = new Signal(); var rec = new Recorder(); - new WhenAnyChangeSink( + _ = new WhenAnyChangeSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value10.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value10.cs index fab9e8c0db..644c0cd62f 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value10.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value10.cs @@ -83,7 +83,7 @@ public async Task ValueSink10_ForwardsError() var e9 = new Signal>(); var e10 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( e1, e2, e3, @@ -116,7 +116,7 @@ public async Task ValueSink10_Completes() var k9 = new Signal>(); var k10 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( k1, k2, k3, @@ -158,7 +158,7 @@ public async Task ValueSink10_SelectorThrows() var t9 = new Signal>(); var t10 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value11.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value11.cs index b064c0469c..56f89fe47c 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value11.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value11.cs @@ -88,7 +88,7 @@ public async Task ValueSink11_ForwardsError() var e10 = new Signal>(); var e11 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( e1, e2, e3, @@ -123,7 +123,7 @@ public async Task ValueSink11_Completes() var k10 = new Signal>(); var k11 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( k1, k2, k3, @@ -168,7 +168,7 @@ public async Task ValueSink11_SelectorThrows() var t10 = new Signal>(); var t11 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value12.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value12.cs index 64019e0537..545aae7601 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value12.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value12.cs @@ -93,7 +93,7 @@ public async Task ValueSink12_ForwardsError() var e11 = new Signal>(); var e12 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( e1, e2, e3, @@ -130,7 +130,7 @@ public async Task ValueSink12_Completes() var k11 = new Signal>(); var k12 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( k1, k2, k3, @@ -178,7 +178,7 @@ public async Task ValueSink12_SelectorThrows() var t11 = new Signal>(); var t12 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value2.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value2.cs index 10da894a47..9be85beba5 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value2.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value2.cs @@ -32,20 +32,20 @@ public async Task ValueSink2_AllPaths() var e1 = new Signal>(); var e2 = new Signal>(); var errRec = new Recorder(); - new WhenAnyValueSink(e1, e2, (x1, x2) => x1 + x2).Subscribe(errRec); + _ = new WhenAnyValueSink(e1, e2, (x1, x2) => x1 + x2).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal>(); var k2 = new Signal>(); var cmpRec = new Recorder(); - new WhenAnyValueSink(k1, k2, (x1, x2) => x1 + x2).Subscribe(cmpRec); + _ = new WhenAnyValueSink(k1, k2, (x1, x2) => x1 + x2).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); var t1 = new Signal>(); var t2 = new Signal>(); var throwRec = new Recorder(); - new WhenAnyValueSink(t1, t2, (_, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyValueSink(t1, t2, (_, _) => throw ex).Subscribe(throwRec); t1.OnNext(Ch("a")); t2.OnNext(Ch("a")); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value3.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value3.cs index 9e3a68d50b..02947c05db 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value3.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value3.cs @@ -36,14 +36,14 @@ public async Task ValueSink3_AllPaths() var e2 = new Signal>(); var e3 = new Signal>(); var errRec = new Recorder(); - new WhenAnyValueSink(e1, e2, e3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(errRec); + _ = new WhenAnyValueSink(e1, e2, e3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal>(); var k2 = new Signal>(); var k3 = new Signal>(); var cmpRec = new Recorder(); - new WhenAnyValueSink(k1, k2, k3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(cmpRec); + _ = new WhenAnyValueSink(k1, k2, k3, (x1, x2, x3) => x1 + x2 + x3).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -52,7 +52,7 @@ public async Task ValueSink3_AllPaths() var t2 = new Signal>(); var t3 = new Signal>(); var throwRec = new Recorder(); - new WhenAnyValueSink(t1, t2, t3, (_, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyValueSink(t1, t2, t3, (_, _, _) => throw ex).Subscribe(throwRec); t1.OnNext(Ch("a")); t2.OnNext(Ch("a")); t3.OnNext(Ch("a")); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value4.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value4.cs index dc0e15436a..4e77f41e2f 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value4.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value4.cs @@ -40,7 +40,7 @@ public async Task ValueSink4_AllPaths() var e3 = new Signal>(); var e4 = new Signal>(); var errRec = new Recorder(); - new WhenAnyValueSink(e1, e2, e3, e4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(errRec); + _ = new WhenAnyValueSink(e1, e2, e3, e4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal>(); @@ -48,7 +48,7 @@ public async Task ValueSink4_AllPaths() var k3 = new Signal>(); var k4 = new Signal>(); var cmpRec = new Recorder(); - new WhenAnyValueSink(k1, k2, k3, k4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(cmpRec); + _ = new WhenAnyValueSink(k1, k2, k3, k4, (x1, x2, x3, x4) => x1 + x2 + x3 + x4).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -59,7 +59,7 @@ public async Task ValueSink4_AllPaths() var t3 = new Signal>(); var t4 = new Signal>(); var throwRec = new Recorder(); - new WhenAnyValueSink(t1, t2, t3, t4, (_, _, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyValueSink(t1, t2, t3, t4, (_, _, _, _) => throw ex).Subscribe(throwRec); t1.OnNext(Ch("a")); t2.OnNext(Ch("a")); t3.OnNext(Ch("a")); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value5.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value5.cs index da1b6a4ae6..1f48526dc8 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value5.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value5.cs @@ -44,7 +44,7 @@ public async Task ValueSink5_AllPaths() var e4 = new Signal>(); var e5 = new Signal>(); var errRec = new Recorder(); - new WhenAnyValueSink(e1, e2, e3, e4, e5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(errRec); + _ = new WhenAnyValueSink(e1, e2, e3, e4, e5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal>(); @@ -53,7 +53,7 @@ public async Task ValueSink5_AllPaths() var k4 = new Signal>(); var k5 = new Signal>(); var cmpRec = new Recorder(); - new WhenAnyValueSink(k1, k2, k3, k4, k5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(cmpRec); + _ = new WhenAnyValueSink(k1, k2, k3, k4, k5, (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -66,7 +66,7 @@ public async Task ValueSink5_AllPaths() var t4 = new Signal>(); var t5 = new Signal>(); var throwRec = new Recorder(); - new WhenAnyValueSink(t1, t2, t3, t4, t5, (_, _, _, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyValueSink(t1, t2, t3, t4, t5, (_, _, _, _, _) => throw ex).Subscribe(throwRec); t1.OnNext(Ch("a")); t2.OnNext(Ch("a")); t3.OnNext(Ch("a")); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value6.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value6.cs index c09e1679a3..d92244ab3d 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value6.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value6.cs @@ -48,7 +48,7 @@ public async Task ValueSink6_AllPaths() var e5 = new Signal>(); var e6 = new Signal>(); var errRec = new Recorder(); - new WhenAnyValueSink(e1, e2, e3, e4, e5, e6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(errRec); + _ = new WhenAnyValueSink(e1, e2, e3, e4, e5, e6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(errRec); e1.OnError(ex); var k1 = new Signal>(); @@ -58,7 +58,7 @@ public async Task ValueSink6_AllPaths() var k5 = new Signal>(); var k6 = new Signal>(); var cmpRec = new Recorder(); - new WhenAnyValueSink(k1, k2, k3, k4, k5, k6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(cmpRec); + _ = new WhenAnyValueSink(k1, k2, k3, k4, k5, k6, (x1, x2, x3, x4, x5, x6) => x1 + x2 + x3 + x4 + x5 + x6).Subscribe(cmpRec); k1.OnCompleted(); k2.OnCompleted(); k3.OnCompleted(); @@ -73,7 +73,7 @@ public async Task ValueSink6_AllPaths() var t5 = new Signal>(); var t6 = new Signal>(); var throwRec = new Recorder(); - new WhenAnyValueSink(t1, t2, t3, t4, t5, t6, (_, _, _, _, _, _) => throw ex).Subscribe(throwRec); + _ = new WhenAnyValueSink(t1, t2, t3, t4, t5, t6, (_, _, _, _, _, _) => throw ex).Subscribe(throwRec); t1.OnNext(Ch("a")); t2.OnNext(Ch("a")); t3.OnNext(Ch("a")); diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value7.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value7.cs index d60404df31..7c0071a12e 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value7.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value7.cs @@ -65,7 +65,7 @@ public async Task ValueSink7_ForwardsError() var e6 = new Signal>(); var e7 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( e1, e2, e3, @@ -92,7 +92,7 @@ public async Task ValueSink7_Completes() var k6 = new Signal>(); var k7 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( k1, k2, k3, @@ -125,7 +125,7 @@ public async Task ValueSink7_SelectorThrows() var t6 = new Signal>(); var t7 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value8.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value8.cs index 0878083047..fb4be67868 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value8.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value8.cs @@ -73,7 +73,7 @@ public async Task ValueSink8_ForwardsError() var e7 = new Signal>(); var e8 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( e1, e2, e3, @@ -102,7 +102,7 @@ public async Task ValueSink8_Completes() var k7 = new Signal>(); var k8 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( k1, k2, k3, @@ -138,7 +138,7 @@ public async Task ValueSink8_SelectorThrows() var t7 = new Signal>(); var t8 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value9.cs b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value9.cs index 15c1e57c31..d360d8101e 100644 --- a/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value9.cs +++ b/src/tests/ReactiveUI.Tests/WhenAny/WhenAnySinkDirectTests.Value9.cs @@ -78,7 +78,7 @@ public async Task ValueSink9_ForwardsError() var e8 = new Signal>(); var e9 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( e1, e2, e3, @@ -109,7 +109,7 @@ public async Task ValueSink9_Completes() var k8 = new Signal>(); var k9 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( k1, k2, k3, @@ -148,7 +148,7 @@ public async Task ValueSink9_SelectorThrows() var t8 = new Signal>(); var t9 = new Signal>(); var rec = new Recorder(); - new WhenAnyValueSink( + _ = new WhenAnyValueSink( t1, t2, t3, diff --git a/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.HighArity.cs b/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.HighArity.cs index 9e73ccf765..05b312103c 100644 --- a/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.HighArity.cs +++ b/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.HighArity.cs @@ -30,7 +30,7 @@ public async Task WhenAnyDynamic_8Props_Selector() var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -65,7 +65,7 @@ public async Task WhenAnyDynamic_8Props_Selector_Distinct() var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -101,7 +101,7 @@ public async Task WhenAnyDynamic_8Props_Selector_NotDistinct() var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -138,7 +138,7 @@ public async Task WhenAnyDynamic_9Props_Selector() var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -175,7 +175,7 @@ public async Task WhenAnyDynamic_9Props_Selector_Distinct() var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -213,7 +213,7 @@ public async Task WhenAnyDynamic_9Props_Selector_NotDistinct() var property8 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property8)); var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -252,7 +252,7 @@ public async Task WhenAnyDynamic_10Props_Selector() var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -291,7 +291,7 @@ public async Task WhenAnyDynamic_10Props_Selector_Distinct() var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -331,7 +331,7 @@ public async Task WhenAnyDynamic_10Props_Selector_NotDistinct() var property9 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property9)); var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -372,7 +372,7 @@ public async Task WhenAnyDynamic_11Props_Selector() var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); var property11 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property11)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -413,7 +413,7 @@ public async Task WhenAnyDynamic_11Props_Selector_Distinct() var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); var property11 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property11)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -455,7 +455,7 @@ public async Task WhenAnyDynamic_11Props_Selector_NotDistinct() var property10 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property10)); var property11 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property11)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -498,7 +498,7 @@ public async Task WhenAnyDynamic_12Props_Selector() var property11 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property11)); var property12 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property12)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -541,7 +541,7 @@ public async Task WhenAnyDynamic_12Props_Selector_Distinct() var property11 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property11)); var property12 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property12)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -585,7 +585,7 @@ public async Task WhenAnyDynamic_12Props_Selector_NotDistinct() var property11 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property11)); var property12 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property12)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, diff --git a/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs b/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs index 406228fc86..dd70dc12ce 100644 --- a/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs +++ b/src/tests/ReactiveUI.Tests/WhenAnyDynamicTest.cs @@ -17,7 +17,7 @@ public async Task WhenAnyDynamic_1Props_Selector_Distinct() var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, _ => "x", true).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -35,7 +35,7 @@ public async Task WhenAnyDynamic_1Props_Selector_NotDistinct() var param = System.Linq.Expressions.Expression.Parameter(typeof(TestViewModel), "x"); var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, _ => "x", false).ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -54,7 +54,7 @@ public async Task WhenAnyDynamic_2Props_Selector() var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, (_, _) => "x").ObserveOn(Sequencer.Immediate).Subscribe(list.Add); @@ -73,7 +73,7 @@ public async Task WhenAnyDynamic_2Props_Selector_Distinct() var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, (_, _) => "x", @@ -93,7 +93,7 @@ public async Task WhenAnyDynamic_2Props_Selector_NotDistinct() var property1 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property1)); var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, (_, _) => "x", @@ -114,7 +114,7 @@ public async Task WhenAnyDynamic_3Props_Selector() var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -135,7 +135,7 @@ public async Task WhenAnyDynamic_3Props_Selector_Distinct() var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -157,7 +157,7 @@ public async Task WhenAnyDynamic_3Props_Selector_NotDistinct() var property2 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property2)); var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -180,7 +180,7 @@ public async Task WhenAnyDynamic_4Props_Selector() var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -203,7 +203,7 @@ public async Task WhenAnyDynamic_4Props_Selector_Distinct() var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -227,7 +227,7 @@ public async Task WhenAnyDynamic_4Props_Selector_NotDistinct() var property3 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property3)); var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -252,7 +252,7 @@ public async Task WhenAnyDynamic_5Props_Selector() var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -277,7 +277,7 @@ public async Task WhenAnyDynamic_5Props_Selector_Distinct() var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -303,7 +303,7 @@ public async Task WhenAnyDynamic_5Props_Selector_NotDistinct() var property4 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property4)); var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -330,7 +330,7 @@ public async Task WhenAnyDynamic_6Props_Selector() var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -357,7 +357,7 @@ public async Task WhenAnyDynamic_6Props_Selector_Distinct() var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -385,7 +385,7 @@ public async Task WhenAnyDynamic_6Props_Selector_NotDistinct() var property5 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property5)); var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -414,7 +414,7 @@ public async Task WhenAnyDynamic_7Props_Selector() var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -443,7 +443,7 @@ public async Task WhenAnyDynamic_7Props_Selector_Distinct() var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, @@ -473,7 +473,7 @@ public async Task WhenAnyDynamic_7Props_Selector_NotDistinct() var property6 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property6)); var property7 = System.Linq.Expressions.Expression.Property(param, nameof(TestViewModel.Property7)); var list = new List(); - vm.WhenAnyDynamic( + _ = vm.WhenAnyDynamic( property1, property2, property3, diff --git a/src/tests/ReactiveUI.WinForms.Tests/winforms/ActivationTests.cs b/src/tests/ReactiveUI.WinForms.Tests/winforms/ActivationTests.cs index 61d27fbddd..325524ddff 100644 --- a/src/tests/ReactiveUI.WinForms.Tests/winforms/ActivationTests.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/winforms/ActivationTests.cs @@ -66,7 +66,7 @@ public async Task SmokeTestWindowsForm() var formActivateCount = 0; var formDeActivateCount = 0; - formActivator.Subscribe(activated => + _ = formActivator.Subscribe(activated => { if (activated) { @@ -113,7 +113,7 @@ public async Task SmokeTestUserControl() var userControlActivateCount = 0; var userControlDeActivateCount = 0; - userControlActivator.Subscribe(activated => + _ = userControlActivator.Subscribe(activated => { if (activated) { diff --git a/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingImplementationTests.cs b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingImplementationTests.cs index 39fbdb383f..6b351d97dc 100644 --- a/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingImplementationTests.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingImplementationTests.cs @@ -37,7 +37,7 @@ public async Task CommandBindByNameWireup() var view = new WinformCommandBindView { ViewModel = vm }; var invokeCount = 0; - vm.Command1.Subscribe(_ => ++invokeCount); + _ = vm.Command1.Subscribe(_ => ++invokeCount); // The parameterless command bind is exposed via the view mixin; the binder implementation only offers // the with-parameter overloads (exercised by the other tests in this fixture). @@ -65,7 +65,7 @@ public async Task CommandBindByNameWireupWithParameter() var fixture = new CommandBinderImplementation(); var invokeCount = 0; - vm.Command3.Subscribe(_ => ++invokeCount); + _ = vm.Command3.Subscribe(_ => ++invokeCount); var disp = fixture.BindCommand(vm, view, vm => vm.Command3, v => v.Command1, vm => vm.Parameter); @@ -109,7 +109,7 @@ public async Task CommandBindToExplicitEventWireupWithParameter() var fixture = new CommandBinderImplementation(); var invokeCount = 0; - vm.Command3.Subscribe(_ => ++invokeCount); + _ = vm.Command3.Subscribe(_ => ++invokeCount); var disp = fixture.BindCommand(vm, view, x => x.Command3, x => x.Command2, vm => vm.Parameter, "MouseUp"); diff --git a/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingTests.cs b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingTests.cs index f34047c089..61933fc8a6 100644 --- a/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingTests.cs +++ b/src/tests/ReactiveUI.WinForms.Tests/winforms/CommandBindingTests.cs @@ -169,4 +169,58 @@ public async Task CommandBinderAffectsEnabledStateForComponents() await Assert.That(input.Enabled).IsFalse(); } } + + /// Binding to a named event invokes the command when that event fires. + /// A representing the asynchronous unit test. + [Test] + public async Task BindCommandToObject_WithEventName_BindsToNamedEvent() + { + var fixture = new CreatesWinformsCommandBinding(); + var executed = false; + var cmd = ReactiveCommand.Create(_ => executed = true); + var input = new Button(); + + using (fixture.BindCommandToObject(cmd, input, Signal.Emit((object)CommandParameter), nameof(Button.Click))) + { + input.PerformClick(); + + await Assert.That(executed).IsTrue(); + } + } + + /// Binding via non-generic add/remove handlers invokes the command when the event fires. + /// A representing the asynchronous unit test. + [Test] + public async Task BindCommandToObject_WithNonGenericHandlers_BindsToEvent() + { + var fixture = new CreatesWinformsCommandBinding(); + var executed = false; + var cmd = ReactiveCommand.Create(_ => executed = true); + var input = new Button(); + + using (fixture.BindCommandToObject